1#!/usr/local/bin/python3.8
2
3"""Provide an application dock applet for the MATE panel
4
5Create a Mate panel applet and handle events generated
6by it
7
8Note: Functionality for docked apps is provided in docked_app.py
9
10      Function for the dock is provided in dock.py
11
12"""
13
14# Copyright (C) 1997-2003 Free Software Foundation, Inc.
15#
16# This program is free software; you can redistribute it and/or
17# modify it under the terms of the GNU General Public License as
18# published by the Free Software Foundation; either version 2 of the
19# License, or (at your option) any later version.
20#
21# This program is distributed in the hope that it will be useful, but
22# WITHOUT ANY WARRANTY; without even the implied warranty of
23# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
24# General Public License for more details.
25#
26# You should have received a copy of the GNU General Public License
27# along with this program; if not, write to the Free Software
28# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
29# 02110-1301, USA.
30#
31# Author:
32#     Robin Thompson
33
34# do not change the value of this variable - it will be set during build
35# according to the value of the --with-gtk3 option used with .configure
36build_gtk2 = False
37
38import gi
39
40if build_gtk2:
41    gi.require_version("Gtk", "2.0")
42    gi.require_version("Wnck", "1.0")
43else:
44    gi.require_version("Gtk", "3.0")
45    gi.require_version("Wnck", "3.0")
46
47gi.require_version("MatePanelApplet", "4.0")
48
49import os
50import sys
51import threading
52sys.path.insert(1, '@pythondir@')
53
54from Xlib.display import Display
55from Xlib import X, error
56from gi.repository import Gtk
57from gi.repository import MatePanelApplet
58from gi.repository import Gdk
59from gi.repository import Gio
60from gi.repository import GObject
61from gi.repository import GLib
62from gi.repository import Wnck
63
64import xdg.DesktopEntry as DesktopEntry
65from urllib.parse import urlparse
66
67import docked_app
68import dock
69
70from log_it import log_it as log_it
71
72drag_dropped = False   # nasty global var used to keep track of whether or not a drag-drop event has occurred
73
74# define a list of keyboard shortcuts to be used to activate specific apps in the dock
75# '<Super>1' to '<Super>0' will correspond to apps 1 to 10
76# '<Super><Alt>1' to '<Super><Alt>9' will correspond to apps 11 to 20
77keyb_shortcuts = ["<Super>1", "<Super>2", "<Super>3", "<Super>4", "<Super>5",
78                  "<Super>6", "<Super>7", "<Super>8", "<Super>9", "<Super>0",
79                  "<Super><Alt>1", "<Super><Alt>2", "<Super><Alt>3", "<Super><Alt>4", "<Super><Alt>5",
80                  "<Super><Alt>6", "<Super><Alt>7", "<Super><Alt>8", "<Super><Alt>9", "<Super><Alt>0"]
81
82
83def applet_button_press(widget, event, the_dock):
84    """Button press event for the applet
85
86    Handle right button press events only
87
88    Find the app that was right clicked and make a record of it
89
90    Args:
91        widget : the widget that was clicked
92        event : the event args
93        the_dock : the Dock object
94    """
95
96    # we don't get click events for the right mouse button presumably
97    # because the panel hijacks them in order to produce the context menu
98    # However, we do get button press event for the right mouse button,
99    # so we can do what we need to do here ....
100    if event.button == 3:
101        # right click, so save the app that was clicked because
102        # the_dock.app_with_mouse is going to be set to None when the
103        # right click menu appears and we move the mouse over the menu to
104        # select an option
105        app = the_dock.get_app_at_mouse(event.x, event.y)
106        the_dock.right_clicked_app = app
107
108        # because the right click menu is about to be shown, we need to hide
109        # the window list
110        the_dock.hide_win_list()
111        the_dock.hide_act_list()
112    elif event.button == 1:
113
114        if the_dock.panel_expand is not True:
115            # prevent the panel acting on the button press that we got
116            # e.g. so that dragging dock icons does not also start a drag of the panel
117            GObject.signal_stop_emission_by_name(widget, "button_press_event")
118
119        dx, dy = the_dock.get_drag_coords()
120        if (dx == -1) and (dy == -1):
121            the_dock.set_drag_coords(event.x, event.y)
122
123
124def applet_button_release(widget, event, the_dock):
125    """Button press event for the applet
126
127    Handle left button release events only
128
129    If the button is released over a non-running app, start the app
130
131    If the button is released over a running app that isn't on the
132    current workspace, change workspace
133
134    If the button is released over a running app:
135        If the app has only a single window open, activate it
136        If window list is showing, hide it
137        If the window list is not visible, show it
138
139    Since the button has been released, make sure that
140    icon dragging doesn't occur
141
142    Args:
143        widget : the widget that registered the release event
144        event : the event args
145        the_dock : the Dock object
146
147    """
148
149    if event.button == 1:
150        the_dock.clear_drag_coords()
151
152        # hide popups
153        the_dock.hide_act_list()
154
155        app = the_dock.get_app_at_mouse(event.x, event.y)
156        if app is not None:
157
158            start_app = app.is_running() is False
159            start_app = start_app | (event.state &
160                                     Gdk.ModifierType.SHIFT_MASK) != 0
161            if start_app:
162                app.start_app()
163            else:
164                if the_dock.win_switch_unity_style:
165                    if app.is_active and app.get_num_windows() > 1:
166                        the_dock.do_window_selection(app)
167                    else:
168                        the_dock.minimize_or_restore_windows(app, True)
169                else:
170                    # if the app only has a single window minimize or restore it, otherwise
171                    # perform the action specified by the user
172                    if app.get_num_windows() == 1:
173                        the_dock.minimize_or_restore_windows(app)
174                    else:
175                        the_dock.do_window_selection(app)
176
177
178    # See https://bugs.launchpad.net/ubuntu-mate/+bug/1554128
179    if event.button == 2:
180        app = the_dock.get_app_at_mouse(event.x, event.y)
181        if app is not None:
182            the_dock.hide_win_list()
183            the_dock.hide_act_list()
184            app.start_app()
185
186
187def applet_enter_notify(widget, event, the_dock):
188    """Enter notify event for the applet
189
190    Brighten the icon of the app which the mouse is currently over
191
192    If another app is currently brightened, darken it to normal
193
194    Set up the right click menu for the dock based on the app which
195    the mouse is currently over
196
197    Start the timer for showing app window lists
198
199    Args:
200        widget : the widget that registered the event i.e. the applet
201        event : the event args
202        the_dock : the Dock object
203    """
204
205    # get the app underneath the mouse cursor
206    app = the_dock.get_app_at_mouse(event.x, event.y)
207
208    # if an app is currently highlighted, de-highlight it
209    if the_dock.app_with_mouse is not None:
210
211        the_dock.app_with_mouse.has_mouse = False
212        the_dock.app_with_mouse.queue_draw()
213        the_dock.app_with_mouse = None
214
215    # highlight the app under the mouse cursor
216    if app is not None:
217        app.has_mouse = True
218        app.queue_draw()
219
220        the_dock.app_with_mouse = app
221
222        # set up the available options for the app
223        the_dock.set_actions_for_app(app)
224    else:
225        the_dock.app_with_mouse = None
226
227
228def applet_leave_notify(widget, event, the_dock):
229    """Leave notify event handle for the applet
230
231    Unbright any brightened app icon
232
233    Args:
234        widget : the widget that registered the event i.e. the applet
235        event : the event args
236        the_dock : the Dock object
237    """
238
239    if the_dock.app_with_mouse is not None:
240        the_dock.app_with_mouse.has_mouse = False
241        the_dock.app_with_mouse.queue_draw()
242        the_dock.app_with_mouse = None
243
244        the_dock.stop_act_list_timer()
245        if the_dock.scrolling:
246            the_dock.stop_scroll_timer()
247
248
249def applet_motion_notify(widget, event, the_dock):
250    """Motion notify event for the applet
251
252    If the docked app under the mouse cursor does not have its icon
253    brightened and another app has a brightened icon then darken the other app
254    # icon and reset the applet tooltip text
255
256    Then, if the docked app under the mouse cursor does not have its icon
257    brightened then brighten it and setup the applet right click menu
258
259    Args:
260        widget : the widget that registered the event i.e. the applet
261        event : the event args
262        the_dock : the Dock object
263    """
264
265    app = the_dock.get_app_at_mouse(event.x, event.y)
266
267    if (the_dock.app_with_mouse is not None) and \
268       (the_dock.app_with_mouse != app):
269        the_dock.app_with_mouse.has_mouse = False
270        the_dock.app_with_mouse.queue_draw()
271
272        widget.queue_draw()
273
274        # because a new app is highlighted reset the window list timer and hide
275        # any currently open window list and action list
276        the_dock.hide_win_list()
277        the_dock.reset_act_list_timer()
278        the_dock.hide_act_list()
279
280    if app is not None:
281
282        the_dock.app_with_mouse = app
283
284        # reset the window list timer
285        the_dock.reset_act_list_timer()
286
287        if the_dock.scrolling and app.scroll_dir != docked_app.ScrollType.SCROLL_NONE:
288            the_dock.reset_scroll_timer()
289
290        if app.has_mouse is False:
291            app.has_mouse = True
292            app.queue_draw()
293            the_dock.app_with_mouse = app
294            the_dock.set_actions_for_app(app)
295
296    else:
297        the_dock.app_with_mouse = None
298
299        the_dock.set_actions_for_app(None)
300
301    dx, dy = the_dock.get_drag_coords()
302    if (dx != -1) and (dy != -1) and not the_dock.dragging:
303        # we may need to begin a drag operation
304
305        if widget.drag_check_threshold(dx, dy, event.x, event.y):
306            target_list = widget.drag_dest_get_target_list()
307            context = widget.drag_begin_with_coordinates(target_list,
308                                                         Gdk.DragAction.MOVE, 1,
309                                                         event, -1, -1)
310
311            applet_drag_begin(widget, context, the_dock)
312
313
314def applet_change_orient(applet, orient, the_dock):
315    """Handler for applet change orientation event
316
317    Set the dock to the new orientation and re-show the applet
318
319    Args:
320        applet : the widget that registered the event i.e. the applet
321        orient : the new orientation
322        the_dock : the Dock object
323    """
324
325    the_dock.set_new_orientation(orient)
326    the_dock.applet.show_all()
327    the_dock.show_or_hide_app_icons()
328
329
330def applet_size_allocate(applet, allocation, the_dock):
331    """ When the applet can play nicely with panel, ensure that it
332        fits within the allocated space
333
334
335    Args :
336        applet : the applet
337        allocation : a Gtk.Allocation - the space in which the applet must
338                     fit
339        the_dock : the Dock object
340    """
341
342    if the_dock.nice_sizing:
343        the_dock.fit_to_alloc()
344    return
345
346
347def applet_change_size(applet, size, the_dock):
348    """Handler for the applet change size event
349
350    Resize the icon and recalculate the minimize location of each app in the
351    dock
352
353    Args:
354        applet : the widget that registered the event i.e. the applet
355        size : the new applet size
356        the_dock : the Dock object
357    """
358
359    for app in the_dock.app_list:
360        the_dock.set_app_icon(app, size)
361
362
363def applet_scroll_event(applet, event, the_dock):
364    """ Handler for the scroll event
365
366    Call the dock's  function to move forward/backward through the active app's
367    windows
368
369    """
370
371    # with V0.81 the dock contains a scrolled window and we now only get
372    # a ScrollDirection of SMOOTH here ....
373    if event.direction == Gdk.ScrollDirection.SMOOTH:
374        hasdeltas, dx, dy = event.get_scroll_deltas()
375        if dy < 0:
376            the_dock.do_window_scroll(Gdk.ScrollDirection.DOWN, event.time)
377        elif dy > 0:
378            the_dock.do_window_scroll(Gdk.ScrollDirection.UP, event.time)
379
380
381def applet_drag_begin(applet, context, the_dock):
382    """
383        Let the dock know we're dragging an icon.
384        Redraw the icon of the app that's being dragged so that the user has
385        visual feedback that the drag has started
386        Set the drag cursor to the app icon
387        Start a timer to monitor the mouse x,y and move the dragged app icon
388        around the dock accordingly
389
390    """
391
392    # we can sometimes get spurious applet-leave events just before a drag
393    # commences. This causes app_with_mouse to be set to None. Therefore we
394    # may need to identify the app under the mouse ourselves...
395
396    if the_dock.app_with_mouse is None:
397        the_dock.app_with_mouse = the_dock.get_app_under_mouse()
398
399    if the_dock.app_with_mouse is not None:
400        the_dock.app_with_mouse.set_dragee(True)
401        the_dock.app_with_mouse.queue_draw()
402
403        Gtk.drag_set_icon_pixbuf(context, the_dock.app_with_mouse.app_pb,
404                                 0, 0)
405
406        the_dock.start_drag_motion_timer(the_dock.app_with_mouse)
407        the_dock.dragging = True
408
409        # finally, hide the window list if it was being shown
410        the_dock.hide_win_list()
411        the_dock.hide_act_list()
412        the_dock.stop_scroll_timer()
413
414
415def applet_drag_data_get(widget, drag_context, data, info, time):
416    """
417        Handler the for drag-data-get event
418
419        Set some dummy text as data for the drag and drop
420    """
421
422    data.set_text("", -1)
423
424
425def applet_drag_drop(widget, context, x, y, time, the_dock):
426    """
427        Handler for the drag-drop event
428
429        The drag drop is over so:
430            Call Gtk.drag-finish and indicate the drag and drop completed ok
431            Let the dock know that the drag and drop has finished and redraw
432            the dragged app's icon
433            Stop the timer that monitors the mouse position
434    """
435
436    app = the_dock.get_dragee()
437    if app is not None:
438        the_dock.stop_drag_motion_timer()
439        app.set_dragee(False)
440        app.queue_draw()
441        Gtk.drag_finish(context, True, False, time)
442    else:
443        # set the drag_dropped module level var so that the drag_data_received event knows
444        # the dnd needs to finish
445        global drag_dropped
446        drag_dropped = True
447
448        target = widget.drag_dest_find_target(context, None)
449        widget.drag_get_data(context, target, time)
450        return True
451
452
453def applet_drag_data_received(widget, drag_context, x, y, data, info, time, the_dock):
454    """ Called when data has been requested from an external source
455        during a drag drop operation
456
457    Examine the data - if it is a .desktop file and the app it relates to
458    is not already in the dock, add it. If the data isn't a .desktop file
459    and the app under the mouse cursor is running, activate it so that
460    the dragged data can be dropped there...
461
462    :param widget: the widget responsible for the event
463    :param drag_context: the dnd context
464    :param x: the x position of the mouse
465    :param y:  y the y position of the mouse
466    :param data: the dragged data
467    :param info:
468    :param time:  the time of the event
469    :param the_dock: the dock ....
470    """
471
472    # examine the data  -did we get any uris ?
473    uri_list = data.get_uris()
474    if (uri_list is not None) and (len(uri_list) > 0):
475        # when dragging .desktop files to the dock we only allow one to be added at
476        # a time. Therefore we're only interested in the first item in the list
477        uri = urlparse(uri_list[0])
478        if uri.scheme == "file":
479            # we're looking for a .desktop file
480            if (uri.path != "") and (os.path.split(uri.path)[1].endswith(".desktop")) and \
481               (os.path.exists(uri.path)):
482
483                # we've got a .desktop file, so if it has been dropped we may need
484                # to add it to the dock
485                global drag_dropped
486                if drag_dropped:
487                    # add the .desktop file to the dock if it is not already there,,,
488                    the_dock.add_app_to_dock(uri.path)
489
490                    # cancel the dnd
491                    Gtk.drag_finish(drag_context, True, False, time)
492                    drag_dropped = False
493                    return
494                else:
495                    # the dnd continues ....
496                    Gdk.drag_status(drag_context, Gdk.DragAction.COPY, time)
497                    return
498
499    # this is not a .desktop so we need to activate the app under the mouse
500    tgt_app = the_dock.get_app_under_mouse()
501    the_dock.start_da_timer(tgt_app)
502    Gdk.drag_status(drag_context, Gdk.DragAction.COPY, time)
503
504
505def applet_drag_end(widget, context, the_dock):
506    """
507    Handler for the drag-end event
508
509    This will be triggered when e.g. the use drags an icon off the panel and
510    releases the mouse button ....
511    Let the dock know that the drag and drop has finished and redraw the
512    dragged app's icon
513    Stop the timer that monitors the mouse position
514    """
515
516    the_dock.stop_drag_motion_timer()
517
518    app = the_dock.get_dragee()
519    if app is not None:
520        app.set_dragee(False)
521        app.queue_draw()
522
523    the_dock.dragging = False
524    the_dock.clear_drag_coords()
525
526
527def applet_drag_motion(widget, context, x, y, time, the_dock):
528    """ Handler for the drag-motion event
529
530    :param widget:  - the applet
531    :param context: - the dnd context
532    :param x:       - x coord of the mouse
533    :param y:       - y coord of the mouse
534    :param time:    - the time of the event
535    :param the_dock - the dock
536    :return:
537    """
538
539    # if the applet isn't dragging an app icon, we may need to examine
540    # the dragged data to see what we are dragging
541    app = the_dock.get_dragee()
542    if app is None:
543        # examine the dragged data so we can decide what to do...
544        tgts = context.list_targets()
545        for t in tgts:
546            if t.name() == "text/uri-list":
547                # if the data contains uris, we need to request the data to
548                # see if it contains a .desktop file
549                widget.drag_get_data(context, t, time)
550                return True
551
552        # if the dragged data is anything other than a uri, we just need to activate the app under
553        # the mouse...
554        tgt_app = the_dock.get_app_under_mouse()
555        the_dock.start_da_timer(tgt_app)
556        return True
557    else:
558        # continue the dnd...
559        Gdk.drag_status(context, Gdk.DragAction.COPY, time)
560
561    return True
562
563
564def applet_shortcut_handler(keybinder, the_dock):
565    """ Handler for global keyboard shortcut presses
566
567    Start the app if it isn't already running
568
569    If it is already runnning cycle through its windows ...
570
571    :param keybinder: the keybinder object with the keystring which was pressed e.g. "<Super>4"
572    :param the_dock: the dock...
573    """
574    # get the position in the dock of the app we need to activate
575    if keybinder.current_shortcut in keybinder.shortcuts:
576        app_no = keybinder.shortcuts.index(keybinder.current_shortcut)
577
578    app = the_dock.get_app_by_pos(app_no)
579    if app is not None:
580        start_app = app.is_running() is False
581        if start_app:
582            app.start_app()
583        else:
584
585            # if the app only has a single window minimize or restore it
586            # otherwise scroll through all available windows
587            if app.get_num_windows() == 1:
588                the_dock.minimize_or_restore_windows(app)
589            else:
590                the_dock.do_window_scroll(Gdk.ScrollDirection.DOWN, 0, app)
591
592
593def applet_fill(applet):
594    """
595    Create the applet
596
597    Register the events that we're interested in getting events for and
598    connect event handlers for them
599
600    Create a dock and add it V/HBox to the applet
601
602
603    Args:
604        applet : the applet
605    """
606
607    os.chdir(os.path.expanduser("~"))
608
609    applet.set_events(applet.get_events() |
610                      Gdk.EventMask.BUTTON_PRESS_MASK |
611                      Gdk.EventMask.BUTTON_RELEASE_MASK |
612                      Gdk.EventMask.POINTER_MOTION_MASK |
613                      Gdk.EventMask.KEY_PRESS_MASK |
614                      Gdk.EventMask.KEY_RELEASE_MASK |
615                      Gdk.EventMask.SCROLL_MASK |
616                      Gdk.EventMask.STRUCTURE_MASK)
617
618    the_dock = dock.Dock(applet)
619    the_dock.setup_dock()
620
621    if the_dock.nice_sizing:
622        applet.set_flags(MatePanelApplet.AppletFlags.EXPAND_MAJOR |
623                         MatePanelApplet.AppletFlags.EXPAND_MINOR |
624                         MatePanelApplet.AppletFlags.FLAGS_NONE)
625    else:
626        applet.set_flags(AppletFlags.FLAGS_NONE)
627
628    if build_gtk2:
629        applet.add(the_dock.box)
630    else:
631        applet.add(the_dock.scrolled_win)
632
633    applet.show_all()
634
635    # make sure that apps pinned to specific workspaces other than the current one
636    # are hidden
637    the_dock.show_or_hide_app_icons()
638
639    applet.connect("enter-notify-event", applet_enter_notify, the_dock)
640    applet.connect("leave-notify-event", applet_leave_notify, the_dock)
641    applet.connect("motion-notify-event", applet_motion_notify, the_dock)
642    applet.connect("button-press-event", applet_button_press, the_dock)
643    applet.connect("button-release-event", applet_button_release, the_dock)
644    applet.connect("change-orient", applet_change_orient, the_dock)
645    applet.connect("change-size", applet_change_size, the_dock)
646    applet.connect("scroll-event", applet_scroll_event, the_dock)
647    applet.connect("size-allocate", applet_size_allocate, the_dock)
648
649    if not build_gtk2:
650        # set up drag and drop - gtk3 only
651        # NOTE: we don't get drag-motion events when dragging app icons within the
652        # dock, making it difficult to tell where the mouse pointer is.....
653        # To get around this, dock.py now contains a timer to monitor the
654        # mouse x.y during these sorts of drag and drops.
655        # drag-motion events do fire when dropping from other apps (e.g. caja)
656        # and the drag-motion event is used in these cases
657
658        # we allow .desktop files to be dropped on the applet, so....
659        drag_tgts = [Gtk.TargetEntry.new("text/uri-list", 0, 0)]
660        applet.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, drag_tgts,
661                               Gdk.DragAction.MOVE)
662
663        drag_tgts = [Gtk.TargetEntry.new("text/uri-list", 0, 0)]
664        applet.drag_dest_set(Gtk.DestDefaults.MOTION, None, Gdk.DragAction.COPY)
665        applet.drag_dest_add_image_targets()
666        applet.drag_dest_add_text_targets()
667        applet.drag_dest_add_uri_targets()
668
669        applet.connect("drag-data-get", applet_drag_data_get)
670        applet.connect("drag-drop", applet_drag_drop, the_dock)
671        applet.connect("drag-end", applet_drag_end, the_dock)
672        applet.connect("drag-motion", applet_drag_motion, the_dock)
673        applet.connect("drag-data-received", applet_drag_data_received, the_dock)
674
675    # set up keyboard shortcuts used to activate apps in the dock
676    keybinder = GlobalKeyBinding()
677    for shortcut in keyb_shortcuts:
678        keybinder.grab(shortcut)
679    keybinder.connect("activate", applet_shortcut_handler, the_dock)
680    keybinder.start()
681
682    applet.set_background_widget(applet)  # hack for panel transparency
683
684
685def applet_factory(applet, iid, data):
686    """Factory routine called when an applet needs to be created
687
688    Create a dock applet if necessary
689
690    Args:
691        applet : the applet
692        iid    : the id of the applet that needs to be created
693        data   :
694    Returns:
695        True if we created a dock applet, False otherwise
696    """
697
698    if iid != "DockApplet":
699        return False
700
701    applet_fill(applet)
702
703    return True
704
705
706class GlobalKeyBinding(GObject.GObject, threading.Thread):
707    __gsignals__ = {
708        'activate': (GObject.SignalFlags.RUN_LAST, None, ()),
709    }
710
711    def __init__(self):
712        GObject.GObject.__init__(self)
713        threading.Thread.__init__(self)
714        self.setDaemon(True)
715
716        self.display = Display()
717        self.screen = self.display.screen()
718        self.window = self.screen.root
719        self.keymap = Gdk.Keymap().get_default()
720        self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask)
721        self.map_modifiers()
722        self.shortcuts = []
723
724    def get_mask_combinations(self, mask):
725        return [x for x in range(mask + 1) if not (x & ~mask)]
726
727    def map_modifiers(self):
728        gdk_modifiers = (Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK,
729                         Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK,
730                         Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK)
731        self.known_modifiers_mask = 0
732        for modifier in gdk_modifiers:
733            if "Mod" not in Gtk.accelerator_name(0, modifier) or "Mod4" in Gtk.accelerator_name(0, modifier):
734                self.known_modifiers_mask |= modifier
735
736    def idle(self):
737        self.emit("activate")
738        return False
739
740    def activate(self):
741        GLib.idle_add(self.run)
742
743    def grab(self, shortcut):
744        keycode = None
745        accelerator = shortcut.replace("<Super>", "<Mod4>")
746        keyval, modifiers = Gtk.accelerator_parse(accelerator)
747
748        try:
749            keycode = self.keymap.get_entries_for_keyval(keyval).keys[0].keycode
750        except AttributeError:
751            # In older Gtk3 the get_entries_for_keyval() returns an unnamed tuple...
752            keycode = self.keymap.get_entries_for_keyval(keyval)[1][0].keycode
753        modifiers = int(modifiers)
754        self.shortcuts.append([keycode, modifiers])
755
756        # Request to receive key press/release reports from other windows that may not be using modifiers
757        catch = error.CatchError(error.BadWindow)
758        self.window.change_attributes(onerror=catch, event_mask=X.KeyPressMask)
759        if catch.get_error():
760            return False
761
762        catch = error.CatchError(error.BadAccess)
763        for ignored_mask in self.ignored_masks:
764            mod = modifiers | ignored_mask
765            result = self.window.grab_key(keycode, mod, True, X.GrabModeAsync, X.GrabModeAsync, onerror=catch)
766        self.display.flush()
767        if catch.get_error():
768            return False
769        return True
770
771    def run(self):
772        self.running = True
773        while self.running:
774            event = self.display.next_event()
775            if (hasattr(event, 'state')):
776                modifiers = event.state & self.known_modifiers_mask
777                self.current_shortcut = None
778                if event.type == X.KeyPress and [event.detail, modifiers] in self.shortcuts:
779                    # Track this shortcut to know which app to activate
780                    self.current_shortcut = [event.detail, modifiers]
781                    GLib.idle_add(self.idle)
782                    self.display.allow_events(X.AsyncKeyboard, event.time)
783                else:
784                    self.display.allow_events(X.ReplayKeyboard, event.time)
785
786    def stop(self):
787        self.running = False
788        self.ungrab()
789        self.display.close()
790
791    def ungrab(self):
792        for shortcut in self.shortcuts:
793            self.window.ungrab_key(shortcut[0], X.AnyModifier, self.window)
794
795
796MatePanelApplet.Applet.factory_main("DockAppletFactory", True,
797                                    MatePanelApplet.Applet.__gtype__,
798                                    applet_factory, None)
799
800
801def main():
802    """Main function.
803
804    Debugging code can go here
805    """
806    pass
807
808
809if __name__ == "__main__":
810    main()
811