summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorTed Alff2020-09-09 21:03:35 -0400
committerTed Alff2020-09-09 21:03:35 -0400
commit6e8f07e4ab53fe5538f68b0a53b1bfe98fb8eda8 (patch)
treec5d546db1680e9d4b64877ec031c5089df12c919
parent83153ae9eab3c6c301a2e013f11a8b662d3e3ba2 (diff)
downloadaur-6e8f07e4ab53fe5538f68b0a53b1bfe98fb8eda8.tar.gz
Include patch in sources rather than download it, since it could change if more commits are added before it's merged.
-rw-r--r--.SRCINFO6
-rw-r--r--PKGBUILD8
-rw-r--r--xuzhen_fixes_pr142_20200909.patch1567
3 files changed, 1574 insertions, 7 deletions
diff --git a/.SRCINFO b/.SRCINFO
index 2f944abf29aa..3ff74f46ea71 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,7 +1,7 @@
pkgbase = dockbarx
pkgdesc = TaskBar with groupping and group manipulation
pkgver = 1.0beta+r820+4a5b382
- pkgrel = 2
+ pkgrel = 3
url = https://github.com/M7S/dockbarx
install = dockbarx.install
arch = i688
@@ -26,9 +26,9 @@ pkgbase = dockbarx
optdepends = gconf: export settings from older versions of dockbarx
optdepends = python-lxml: import settings script
source = dockbarx::git+https://github.com/M7S/dockbarx.git#commit=4a5b382f03402e58cbbaaeb2ee3be4fbbb795aba
- source = xuzhen_fixes.patch::https://github.com/M7S/dockbarx/pull/142.patch
- sha256sums = SKIP
+ source = xuzhen_fixes_pr142_20200909.patch
sha256sums = SKIP
+ sha256sums = 7ff9d0d99630f6229e4a8c9f0a9098d5af8f26b363971096ccc69b1143e0b69a
pkgname = dockbarx
diff --git a/PKGBUILD b/PKGBUILD
index eddd41c1730f..0bfa5a48e786 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -3,7 +3,7 @@
pkgname=dockbarx
_pkgver=1.0beta
pkgver=1.0beta+r820+4a5b382
-pkgrel=2
+pkgrel=3
pkgdesc="TaskBar with groupping and group manipulation"
arch=('i688' 'x86_64' 'armv7h' 'aarch64')
url="https://github.com/M7S/dockbarx"
@@ -19,9 +19,9 @@ optdepends=('mate-panel: mate applet'
'python-lxml: import settings script')
_commit='4a5b382f03402e58cbbaaeb2ee3be4fbbb795aba'
source=("${pkgname}::git+https://github.com/M7S/dockbarx.git#commit=${_commit}"
- 'xuzhen_fixes.patch::https://github.com/M7S/dockbarx/pull/142.patch')
+ 'xuzhen_fixes_pr142_20200909.patch')
sha256sums=('SKIP'
- 'SKIP')
+ '7ff9d0d99630f6229e4a8c9f0a9098d5af8f26b363971096ccc69b1143e0b69a')
install="${pkgname}.install"
pkgver() {
@@ -31,7 +31,7 @@ pkgver() {
prepare() {
cd "${srcdir}/${pkgname}"
- patch -Np1 -i ../xuzhen_fixes.patch
+ patch -Np1 -i ../xuzhen_fixes_pr142_20200909.patch
}
package() {
diff --git a/xuzhen_fixes_pr142_20200909.patch b/xuzhen_fixes_pr142_20200909.patch
new file mode 100644
index 000000000000..fd0a52d3ad12
--- /dev/null
+++ b/xuzhen_fixes_pr142_20200909.patch
@@ -0,0 +1,1567 @@
+From b8f102546e590034092e99b422d37b7f7bb9c5b0 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 9 Aug 2020 12:19:23 +0800
+Subject: [PATCH 01/21] fixed issue #140
+
+---
+ dockx | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dockx b/dockx
+index bbc9535..a45a9a2 100755
+--- a/dockx
++++ b/dockx
+@@ -823,11 +823,11 @@ class DockX(CairoDockX):
+ XDisplay.get_atom('CARDINAL'), 32,
+ strut[:4],
+ X.PropModeReplace)
+-
+ topw.change_property(XDisplay.get_atom('_NET_WM_STRUT_PARTIAL'),
+ XDisplay.get_atom('CARDINAL'), 32,
+ strut,
+ X.PropModeReplace)
++ XDisplay.flush()
+
+ def __get_monitor_and_strut_borders(self):
+ # This function returns the distance from screen edges to
+
+From e16735bea70290a6bd4beafe448c8befa06502ee Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 9 Aug 2020 18:17:38 +0800
+Subject: [PATCH 02/21] fixed X11 timestamp
+
+---
+ dockbarx/windowbutton.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/dockbarx/windowbutton.py b/dockbarx/windowbutton.py
+index 2554dc7..751a15a 100644
+--- a/dockbarx/windowbutton.py
++++ b/dockbarx/windowbutton.py
+@@ -21,6 +21,7 @@
+ gi.require_version("Gtk", "3.0")
+ from gi.repository import Gtk
+ from gi.repository import Gdk
++from gi.repository import GdkX11
+ from gi.repository import GdkPixbuf
+ from gi.repository import GLib
+ from gi.repository import Pango
+@@ -225,7 +226,7 @@ def action_select_or_minimize_window(self, widget=None,
+ if event:
+ t = event.time
+ else:
+- t = 0
++ t = GdkX11.x11_get_server_time(Gdk.get_default_root_window())
+ if self.wnck.get_workspace() is not None \
+ and self.screen.get_active_workspace() != self.wnck.get_workspace():
+ self.wnck.get_workspace().activate(t)
+
+From 2744ee6234ab3bf38875269eaeaaf22a27995e42 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 15 Aug 2020 13:23:56 +0800
+Subject: [PATCH 03/21] fixed locked popup behavior
+
+---
+ dockbarx/cairowidgets.py | 3 +-
+ dockbarx/dockbar.py | 8 ++-
+ dockbarx/groupbutton.py | 107 ++++++++++++++++++++-------------------
+ dockx | 11 ++--
+ 4 files changed, 71 insertions(+), 58 deletions(-)
+
+diff --git a/dockbarx/cairowidgets.py b/dockbarx/cairowidgets.py
+index 3263330..a4967af 100644
+--- a/dockbarx/cairowidgets.py
++++ b/dockbarx/cairowidgets.py
+@@ -556,7 +556,8 @@ def point(self, new_pointer, ap=0):
+
+ def on_draw(self, widget, ctx):
+ #~ self.set_shape_mask()
+- w,h = self.get_size()
++ a = self.get_allocation()
++ w, h = a.width, a.height
+ if self.is_composited():
+ ctx.set_source_rgba(1, 1, 1, 0)
+ else:
+diff --git a/dockbarx/dockbar.py b/dockbarx/dockbar.py
+index 39ffbb0..104476a 100644
+--- a/dockbarx/dockbar.py
++++ b/dockbarx/dockbar.py
+@@ -535,7 +535,7 @@ def load(self):
+
+ self.reload(tell_parent=False)
+
+- def reload(self, event=None, data=None, tell_parent=True):
++ def reload(self, event=None, data=None, tell_parent=True, locked_group=None):
+ """Reloads DockbarX."""
+ logger.info("DockbarX reload")
+ # Clear away the old stuff, if any.
+@@ -609,6 +609,12 @@ def reload(self, event=None, data=None, tell_parent=True):
+ # Initiate group buttons with windows
+ for window in self.screen.get_windows():
+ self.__on_window_opened(self.screen, window)
++ # restore locked popup
++ if locked_group is not None:
++ for group in self.groups:
++ if group.identifier == locked_group:
++ group.add_locked_popup()
++ break
+
+ self.screen.connect("window-opened", self.__on_window_opened)
+ self.screen.connect("window-closed", self.__on_window_closed)
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index 978d3a2..65249e4 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -345,11 +345,11 @@ def show_launch_popup(self):
+ def add_locked_popup(self):
+ if self.locked_popup:
+ return
++ self.popup.hide()
+ locked_popup = self.globals.get_locked_popup()
+ if locked_popup:
+ locked_popup.destroy()
+ self.locked_popup = LockedPopup(self)
+- self.popup.hide()
+ self.globals.set_locked_popup(self.locked_popup)
+ self.locked_popup.show()
+
+@@ -357,6 +357,7 @@ def remove_locked_popup(self):
+ if self.locked_popup:
+ self.locked_popup.destroy()
+ self.popup.hide()
++ self.locked_popup = None
+
+ #### Window handling
+ def add_window(self, wnck_window):
+@@ -2049,7 +2050,7 @@ def get_child_(self):
+ else:
+ return children[0]
+
+- def on_size_allocate(self, widget, allocation):
++ def on_size_allocate(self, widget, allocation, no_move=False):
+ if allocation == self.last_allocation:
+ return
+ group = self.group_r()
+@@ -2060,7 +2061,7 @@ def on_size_allocate(self, widget, allocation):
+ offset = int(self.popup_style.get("%s_distance" % self.popup_type, -7))
+ dummy, wx, wy = window.get_origin()
+ b_alloc = group.button.get_allocation()
+- width, height = self.get_size()
++ width, height = allocation.width, allocation.height
+
+ if Gtk.MAJOR_VERSION > 3 or Gtk.MINOR_VERSION >= 22:
+ mgeo = group.get_monitor().get_geometry();
+@@ -2119,7 +2120,8 @@ def on_size_allocate(self, widget, allocation):
+ x = x + b_alloc.width + offset
+ p = wy + b_alloc.y + (b_alloc.height // 2) - y
+ self.point(direction, p)
+- self.move(x, y)
++ if not no_move:
++ self.move(x, y)
+ try:
+ child_func = self.get_child_().on_popup_reallocate
+ except AttributeError:
+@@ -2286,15 +2288,18 @@ def __init__(self, group):
+ group.window_list.apply_mini_mode()
+ if not group.get_windows():
+ self.hide()
+- else:
+- # not work
+- # GLib.idle_add(self.__on_realized)
+- pass
++ self.set_type_hint(Gdk.WindowTypeHint.DOCK)
++ self.set_keep_above(True)
++ self.set_decorated(False)
++ self.set_accept_focus(False)
++ self.set_resizable(False)
+ self.overlap_sid = self.globals.connect("locked-list-overlap-changed", self.__set_own_strut)
+- self.connect("size-allocate", self.on_size_allocate)
++ self.size_allocate_sid = self.connect("size-allocate", self.on_size_allocate)
++ self.connect("realize", self.__on_realized)
+
+ def show(self):
+- CairoPopup.show(self)
++ CairoPopup.show_all(self)
++ self.on_size_allocate(self, self.get_allocation())
+
+ def hide(self):
+ CairoPopup.hide(self)
+@@ -2315,7 +2320,7 @@ def on_size_allocate(self, widget, allocation):
+ else:
+ mgeo = Gdk.Screen.get_default().get_monitor_geometry(group.get_monitor())
+
+- width, height = self.get_size()
++ width, height = allocation.width, allocation.height
+ if self.dockbar_r().orient in ("down", "up"):
+ button_window = group.button.get_window()
+ if button_window:
+@@ -2326,7 +2331,7 @@ def on_size_allocate(self, widget, allocation):
+ GroupPopup.on_size_allocate(self, widget, allocation)
+ self.__set_own_strut()
+ return
+- GroupPopup.on_size_allocate(self, widget, allocation)
++ GroupPopup.on_size_allocate(self, widget, allocation, no_move=True)
+ strut = self.__get_other_strut(mgeo.width // 2 - width // 2,
+ mgeo.width // 2 + width // 2)
+ self.move(mgeo.width // 2 - width // 2, mgeo.height - height - strut - 1)
+@@ -2340,44 +2345,40 @@ def on_size_allocate(self, widget, allocation):
+ child_func(self)
+
+ def __set_own_strut(self, *args):
+- # Todo: This doesn't work at all. Find out why and uncomment.
+- return
+- #~ global display
+- #~ global X
+- #~ if display is None:
+- #~ from Xlib import display
+- #~ win = self.get_window()
+- #~ if win:
+- #~ if self.globals.settings["locked_list_no_overlap"] is False:
+- #~ topw = XDisplay.create_resource_object('window',
+- #~ win.get_toplevel().get_xid())
+- #~ topw.delete_property(XDisplay.get_atom("_NET_WM_STRUT"))
+- #~ topw.delete_property(XDisplay.get_atom("_NET_WM_STRUT_PARTIAL"))
+- #~ return
+- #~ if X is None:
+- #~ from Xlib import X
+- #~ group = self.group_r()
+- #~ a = self.get_allocation()
+- #~ x, y = self.get_position()
+- #~ if Gtk.MAJOR_VERSION > 3 or Gtk.MINOR_VERSION >= 22:
+- #~ mgeo = group.get_monitor().get_geometry();
+- #~ else:
+- #~ mgeo = Gdk.Screen.get_default().get_monitor_geometry(group.get_monitor())
+- #~ height = mgeo.y + mgeo.height - y
+- #~ x1 = mgeo.x + x
+- #~ x2 = mgeo.x + x + a.width
+- #~ strut = [0, 0, 0, height, 0, 0, 0, 0, 0, 0, x1, x2]
+- #~ topw = XDisplay.create_resource_object('window',
+- #~ win.get_toplevel().get_xid())
+- #~ topw.change_property(XDisplay.get_atom('_NET_WM_STRUT'),
+- #~ XDisplay.get_atom('CARDINAL'), 32,
+- #~ strut[:4],
+- #~ X.PropModeReplace)
+-
+- #~ topw.change_property(XDisplay.get_atom('_NET_WM_STRUT_PARTIAL'),
+- #~ XDisplay.get_atom('CARDINAL'), 32,
+- #~ strut,
+- #~ X.PropModeReplace)
++ win = self.get_window()
++ if not win:
++ return
++ topw = XDisplay.create_resource_object('window',
++ win.get_toplevel().get_xid())
++ if self.globals.settings["locked_list_no_overlap"] is False:
++ topw = XDisplay.create_resource_object('window',
++ win.get_toplevel().get_xid())
++ topw.delete_property(XDisplay.get_atom("_NET_WM_STRUT"))
++ topw.delete_property(XDisplay.get_atom("_NET_WM_STRUT_PARTIAL"))
++ return
++ global X
++ if X is None:
++ from Xlib import X
++ group = self.group_r()
++ a = self.get_allocation()
++ x, y = self.get_position()
++ if Gtk.MAJOR_VERSION > 3 or Gtk.MINOR_VERSION >= 22:
++ mgeo = group.get_monitor().get_geometry();
++ else:
++ mgeo = Gdk.Screen.get_default().get_monitor_geometry(group.get_monitor())
++ height = mgeo.y + mgeo.height - y
++ x1 = max(mgeo.x + x, 0)
++ x2 = max(mgeo.x + x + a.width, 0)
++ strut = [0, 0, 0, height, 0, 0, 0, 0, 0, 0, x1, x2]
++ topw.change_property(XDisplay.get_atom('_NET_WM_STRUT'),
++ XDisplay.get_atom('CARDINAL'), 32,
++ strut[:4],
++ X.PropModeReplace)
++ topw.change_property(XDisplay.get_atom('_NET_WM_STRUT_PARTIAL'),
++ XDisplay.get_atom('CARDINAL'), 32,
++ strut,
++ X.PropModeReplace)
++ XDisplay.flush()
+
+ def __get_other_strut(self, x1, x2):
+ # if Gtk.MAJOR_VERSION > 3 or Gtk.MINOR_VERSION >= 22:
+@@ -2410,15 +2411,15 @@ def __get_other_strut(self, x1, x2):
+ strut = max(strut, prop2.value[3])
+ return strut
+
+- def __on_realized(self):
+- while not self.get_window():
+- Gtk.main_iteration()
++ def __on_realized(self, widget):
++ self.get_window().set_override_redirect(False)
+ self.__set_own_strut()
+
+ def destroy(self):
+ group = self.group_r()
+ group.locked_popup = None
+ self.globals.disconnect(self.overlap_sid)
++ self.disconnect(self.size_allocate_sid)
+ self.childbox.remove(group.window_list)
+ group.popup.set_child_(group.window_list)
+ group.window_list.apply_normal_mode()
+diff --git a/dockx b/dockx
+index a45a9a2..9c6f821 100755
+--- a/dockx
++++ b/dockx
+@@ -1127,7 +1127,7 @@ class DockX(CairoDockX):
+ orient = {"top": "up", "left": "left",
+ "right": "right", "bottom": "down"}[pos]
+ self.dockbar.set_orient(orient)
+- self.reload()
++ self.reload(keep_locked_popup = True)
+
+ def __on_overlap_changed(self, *args):
+ a = self.get_allocation()
+@@ -1290,9 +1290,14 @@ class DockX(CairoDockX):
+ #self.applets.find_applets()
+ self.__rebuild()
+
+- def reload(self, *args):
++ def reload(self, keep_locked_popup=False, *args):
+ self.db_loaded = False
+- self.dockbar.reload(tell_parent=False)
++ if keep_locked_popup:
++ locked_popup = self.globals.get_locked_popup()
++ locked_group_id = locked_popup.group_r().identifier
++ else:
++ locked_group_id = None
++ self.dockbar.reload(tell_parent=False, locked_group=locked_group_id)
+ self.db_loaded = True
+ self.applets.find_applets()
+ self.theme.reload()
+
+From 8d5bf59e35381f8350b09443324f577402e4f858 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 15 Aug 2020 18:41:52 +0800
+Subject: [PATCH 04/21] fixed issue #143
+
+---
+ dockbarx/cairowidgets.py | 2 +-
+ dockx | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/dockbarx/cairowidgets.py b/dockbarx/cairowidgets.py
+index a4967af..ad39c39 100644
+--- a/dockbarx/cairowidgets.py
++++ b/dockbarx/cairowidgets.py
+@@ -487,7 +487,7 @@ def __init__(self, orient="down", no_arrow=False, type_="popup"):
+ gdk_screen = Gdk.Screen.get_default()
+ visual = gdk_screen.get_rgba_visual()
+ if visual is None:
+- visual = gdk_screen.get_rgb_visual()
++ visual = gdk_screen.get_system_visual()
+ self.set_visual(visual)
+ self.set_app_paintable(1)
+ self.globals = Globals()
+diff --git a/dockx b/dockx
+index 9c6f821..0c1d811 100755
+--- a/dockx
++++ b/dockx
+@@ -64,7 +64,7 @@ class CairoDockX(Gtk.Window):
+ gdk_screen = Gdk.Screen.get_default()
+ visual = gdk_screen.get_rgba_visual()
+ if visual is None:
+- visual = gdk_screen.get_rgb_visual()
++ visual = gdk_screen.get_system_visual()
+ self.set_visual(visual)
+ self.set_app_paintable(1)
+ self.compute_padding()
+
+From 57e59bd53954d76eb936b1e9acb49f058b96954d Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 15 Aug 2020 19:20:30 +0800
+Subject: [PATCH 05/21] added existence checking
+
+---
+ dockx | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/dockx b/dockx
+index 0c1d811..af986a3 100755
+--- a/dockx
++++ b/dockx
+@@ -1294,7 +1294,10 @@ class DockX(CairoDockX):
+ self.db_loaded = False
+ if keep_locked_popup:
+ locked_popup = self.globals.get_locked_popup()
+- locked_group_id = locked_popup.group_r().identifier
++ if locked_popup:
++ locked_group_id = locked_popup.group_r().identifier
++ else:
++ locked_group_id = None
+ else:
+ locked_group_id = None
+ self.dockbar.reload(tell_parent=False, locked_group=locked_group_id)
+
+From 0e2712a8469552d91fdda9a0445599703f9543d9 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 15 Aug 2020 19:50:58 +0800
+Subject: [PATCH 06/21] updated README
+
+---
+ README.md | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/README.md b/README.md
+index fde8edd..d428506 100644
+--- a/README.md
++++ b/README.md
+@@ -6,8 +6,8 @@ The gtk3/python3 version of DockbarX is a lightweight taskbar / panel replacemen
+
+ DockbarX is free software and is licensed under GPL3.
+
+-## Install in Ubuntu 20.04+ from ppa
+-The main DockbarX ppa is not maintained for the moment. You can use Xu Zhen's unofficial DockbarX ppa instead. To add the ppa and install the application in Ubuntu (and derivatives), use the following commands:
++## Install in Ubuntu 18.04+ from PPA
++The main DockbarX PPA is not maintained for the moment. You can use Xu Zhen's unofficial DockbarX PPA instead. To add the PPA and install the application in Ubuntu (and derivatives), use the following commands:
+
+ ```
+ sudo add-apt-repository ppa:xuzhen666/dockbarx
+
+From 2fb1f4c618c6536f8b8028ae776a99bf79778222 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 23 Aug 2020 13:47:11 +0800
+Subject: [PATCH 07/21] fixed missing argument
+
+---
+ dockbarx/groupbutton.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index 65249e4..ed05d60 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -783,7 +783,7 @@ def __menu_position(self, menu):
+ x -= w - a.width
+ return (x, y, False)
+
+- def __menu_closed(self, menushell):
++ def __menu_closed(self, menushell=None):
+ # Used only with the gtk menu
+ self.globals.gtkmenu = None
+ self.menu.delete_menu()
+
+From 0308d1bb368c4236ceebf82f2bc54b7a07a6d5a4 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 23 Aug 2020 20:10:29 +0800
+Subject: [PATCH 08/21] a fallback desktop file editor. fixed issue #146
+
+---
+ dockbarx/desktopfileeditor.py | 251 ++++++++++++++++++++++++++++++++++
+ dockbarx/dockbar.py | 20 ++-
+ dockbarx/groupbutton.py | 3 +
+ 3 files changed, 268 insertions(+), 6 deletions(-)
+ create mode 100644 dockbarx/desktopfileeditor.py
+
+diff --git a/dockbarx/desktopfileeditor.py b/dockbarx/desktopfileeditor.py
+new file mode 100644
+index 0000000..1f07531
+--- /dev/null
++++ b/dockbarx/desktopfileeditor.py
+@@ -0,0 +1,251 @@
++#!/usr/bin/python3
++
++# Desktop File Editor for Dockbarx
++#
++# Copyright 2020 Xu Zhen
++#
++# DockbarX is free software: you can redistribute it and/or modify
++# it under the terms of the GNU General Public License as published by
++# the Free Software Foundation, either version 3 of the License, or
++# (at your option) any later version.
++#
++# DockbarX is distributed in the hope that it will be useful,
++# but WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++# GNU General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with dockbar. If not, see <http://www.gnu.org/licenses/>
++
++import configparser
++import os
++import gi
++gi.require_version("Gtk", "3.0")
++from gi.repository import Gtk
++from gi.repository import GdkPixbuf
++from .log import logger
++from . import i18n
++_ = i18n.language.gettext
++
++
++class DesktopFileEditor(Gtk.Dialog):
++ def __init__(self):
++ Gtk.Dialog.__init__(self, _("Edit Launcher"))
++ self.config = configparser.ConfigParser(delimiters=('='))
++ self.config.optionxform = str
++ self.langs = os.environ.get("LANGUAGE")
++ self.keymap = {}
++ self.icon = None
++ self.file = None
++ if self.langs is not None:
++ self.langs = self.langs.split(":")
++ else:
++ self.langs = []
++
++ grid = Gtk.Grid()
++ grid.set_row_spacing(5)
++ grid.set_column_spacing(5)
++ grid.set_margin_start(10)
++ grid.set_margin_end(10)
++ row = 0
++ label = Gtk.Label.new(_("Name:"))
++ label.set_halign(Gtk.Align.END)
++ self.name_entry = Gtk.Entry()
++ self.name_entry.set_hexpand(True)
++ grid.attach(label, 0, row, 1, 1)
++ grid.attach(self.name_entry, 1, row, 1, 1)
++ row += 1
++ label = Gtk.Label.new(_("Comment:"))
++ label.set_halign(Gtk.Align.END)
++ self.comment_entry = Gtk.Entry()
++ self.comment_entry.set_hexpand(True)
++ grid.attach(label, 0, row, 1, 1)
++ grid.attach(self.comment_entry, 1, row, 1, 1)
++ row += 1
++ label = Gtk.Label.new(_("Command:"))
++ label.set_halign(Gtk.Align.END)
++ self.command_entry = Gtk.Entry()
++ self.command_entry.set_hexpand(True)
++ self.command_entry.set_margin_end(5)
++ self.command_button = Gtk.Button()
++ image = Gtk.Image.new_from_icon_name("document-open", Gtk.IconSize.SMALL_TOOLBAR)
++ self.command_button.set_image(image)
++ self.command_button.set_hexpand(True)
++ box = Gtk.HBox()
++ box.pack_start(self.command_entry, True, True, 0)
++ box.pack_start(self.command_button, False, True, 0)
++ grid.attach(label, 0, row, 1, 1)
++ grid.attach(box, 1, row, 1, 1)
++ row += 1
++ label = Gtk.Label.new(_("Working Directory:"))
++ label.set_halign(Gtk.Align.END)
++ self.workdir_entry = Gtk.Entry()
++ self.workdir_entry.set_hexpand(True)
++ self.workdir_entry.set_margin_end(5)
++ self.workdir_button = Gtk.Button()
++ image = Gtk.Image.new_from_icon_name("folder", Gtk.IconSize.SMALL_TOOLBAR)
++ self.workdir_button.set_image(image)
++ box = Gtk.HBox()
++ box.pack_start(self.workdir_entry, True, True, 0)
++ box.pack_start(self.workdir_button, False, True, 0)
++ grid.attach(label, 0, row, 1, 1)
++ grid.attach(box, 1, row, 1, 1)
++ row += 1
++ label = Gtk.Label.new(_("Icon:"))
++ label.set_halign(Gtk.Align.END)
++ self.icon_button = Gtk.Button()
++ self.icon_button.set_size_request(64, 64)
++ box = Gtk.HBox()
++ box.pack_start(self.icon_button, False, True, 0)
++ grid.attach(label, 0, row, 1, 1)
++ grid.attach(box, 1, row, 1, 1)
++ row += 1
++ label = Gtk.Label.new(_("Options:"))
++ label.set_halign(Gtk.Align.END)
++ self.notification_check = Gtk.CheckButton.new_with_label(_("Use startup notification"))
++ grid.attach(label, 0, row, 1, 1)
++ grid.attach(self.notification_check, 1, row, 1, 1)
++ row += 1
++ self.terminal_check = Gtk.CheckButton.new_with_label(_("Run in terminal"))
++ grid.attach(self.terminal_check, 1, row, 1, 1)
++
++ self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
++ self.add_button(_("Save"), Gtk.ResponseType.OK)
++
++ self.command_dialog = Gtk.FileChooserDialog()
++ self.command_dialog.set_title(_("Select an application"))
++ self.command_dialog.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL, _("_OK"), Gtk.ResponseType.OK)
++ self.workdir_dialog = Gtk.FileChooserDialog()
++ self.workdir_dialog.set_title(_("Select a working directory"))
++ self.workdir_dialog.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL, _("_OK"), Gtk.ResponseType.OK)
++ self.workdir_dialog.set_action(Gtk.FileChooserAction.SELECT_FOLDER)
++ self.icon_dialog = Gtk.FileChooserDialog()
++ self.icon_dialog.set_title(_("Select an icon"))
++ self.icon_dialog.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL, _("_OK"), Gtk.ResponseType.OK)
++ icon_filter = Gtk.FileFilter()
++ icon_filter.add_pixbuf_formats()
++ self.icon_dialog.set_filter(icon_filter)
++ self.previewer = Gtk.Image()
++ self.icon_dialog.set_preview_widget(self.previewer)
++ self.icon_dialog.connect("update-preview", self.update_preview)
++
++ self.command_button.connect("clicked", self.select_file, "command")
++ self.workdir_button.connect("clicked", self.select_file, "workdir")
++ self.icon_button.connect("clicked", self.select_image)
++
++ self.set_resizable(False)
++ box = self.get_content_area()
++ box.pack_start(grid, True, True, 10)
++
++ def select_file(self, button, name):
++ dialog = getattr(self, name + "_dialog")
++ entry = getattr(self, name + "_entry")
++ action = dialog.run()
++ if action == Gtk.ResponseType.OK:
++ entry.set_text(dialog.get_filename())
++ dialog.hide()
++
++ def select_image(self, button):
++ action = self.icon_dialog.run()
++ if action == Gtk.ResponseType.OK:
++ filename = self.icon_dialog.get_filename()
++ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, 64, 64)
++ if pixbuf is None:
++ return
++ image = Gtk.Image.new_from_pixbuf(pixbuf)
++ self.icon_button.set_image(image)
++ self.icon = filename
++ self.icon_dialog.hide()
++
++ def update_preview(self, chooser):
++ filename = chooser.get_preview_filename()
++ if filename is None or not os.path.isfile(filename):
++ chooser.set_preview_widget_active(False)
++ return
++ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(filename, 256, 256)
++ if pixbuf is not None:
++ image = chooser.get_preview_widget()
++ image.set_from_pixbuf(pixbuf)
++ chooser.set_preview_widget_active(pixbuf is not None)
++
++ def load(self, filename):
++ self.file = None
++ if not os.path.exists(filename):
++ return False
++ try:
++ self.config.read(filename)
++ except:
++ return False
++ if self.get_item("Type") != "Application":
++ return False
++ self.file = filename
++ self.name_entry.set_text(self.get_item("Name"))
++ self.comment_entry.set_text(self.get_item("Comment"))
++ self.command_entry.set_text(self.get_item("Exec"))
++ self.workdir_entry.set_text(self.get_item("Path"))
++ self.notification_check.set_active(self.get_item("StartupNotify") == "true")
++ self.terminal_check.set_active(self.get_item("Terminal") == "true")
++ icon = self.get_item("Icon")
++ if icon.startswith("/"):
++ pixbuf = GdkPixbuf.new_from_file_at_size(icon, 64, 64)
++ else:
++ icon_theme = Gtk.IconTheme.get_default()
++ pixbuf = icon_theme.load_icon(icon, 64, 0)
++ if pixbuf is not None:
++ self.icon = icon
++ image = Gtk.Image.new_from_pixbuf(pixbuf)
++ self.icon_button.set_image(image)
++ return True
++
++ def save(self, filename=None):
++ if filename is None:
++ filename = self.file
++ if "Desktop Entry" not in self.config.sections():
++ self.config["Desktop Entry"] = {}
++ self.set_item("Name", self.name_entry.get_text())
++ self.set_item("Comment", self.comment_entry.get_text())
++ self.set_item("Exec", self.command_entry.get_text())
++ self.set_item("Path", self.workdir_entry.get_text())
++ self.set_item("StartupNotify", self.notification_check.get_active())
++ self.set_item("Terminal", self.terminal_check.get_active())
++ self.set_item("Icon", self.icon)
++ try:
++ f = open(filename, "w", encoding="utf-8")
++ self.config.write(f, space_around_delimiters=False)
++ f.close()
++ return True
++ except:
++ logger.error("failed to save launcher: %s" % filename)
++ return False
++
++ def get_item(self, name):
++ if name in ("Name", "Comment", "GenericName"):
++ keys = [ name + "[%s]" % l for l in self.langs ]
++ keys.append(name)
++ else:
++ keys = [ name ]
++ for key in keys:
++ try:
++ text = self.config.get("Desktop Entry", key, raw=True)
++ except:
++ pass
++ else:
++ self.keymap[name] = key
++ return text
++ return ""
++
++ def set_item(self, name, value):
++ if name in self.keymap:
++ name = self.keymap[name]
++ try:
++ if type(value) == bool:
++ if value:
++ value = "true"
++ else:
++ value = "false"
++ if value is None or value == "":
++ self.config.remove_option("Desktop Entry", name)
++ else:
++ text = self.config.set("Desktop Entry", name, value)
++ except:
++ pass
+diff --git a/dockbarx/dockbar.py b/dockbarx/dockbar.py
+index 104476a..f6ec793 100644
+--- a/dockbarx/dockbar.py
++++ b/dockbarx/dockbar.py
+@@ -493,6 +493,8 @@ def load(self):
+ from .dbx_dbus import DockbarDBus
+ global UnityWatcher
+ from .unity import UnityWatcher
++ global DesktopFileEditor
++ from .desktopfileeditor import DesktopFileEditor
+ global shlex
+ import shlex
+
+@@ -1412,13 +1414,19 @@ def edit_launcher(self, path, identifier):
+ "mate-desktop-item-edit", "exo-desktop-item-edit")
+ for program in programs:
+ if check_program(program):
++ process = subprocess.Popen([program, new_path], env=os.environ)
++ GLib.timeout_add(100, self.__wait_for_launcher_editor,
++ process, path, new_path, identifier)
+ break
+ else:
+- logger.warning("Error: Found no program for editing .desktop files.")
+- return
+- process = subprocess.Popen([program, new_path], env=os.environ)
+- GLib.timeout_add(100, self.__wait_for_launcher_editor,
+- process, path, new_path, identifier)
++ editor = DesktopFileEditor()
++ editor.load(path)
++ editor.show_all()
++ action = editor.run()
++ if action == Gtk.ResponseType.OK:
++ editor.save(new_path)
++ self.__wait_for_launcher_editor(None, path, new_path, identifier)
++ editor.destroy()
+
+ def update_pinned_apps_list(self, arg=None):
+ # Saves pinned_apps_list
+@@ -1591,7 +1599,7 @@ def __identifier_dialog(self, identifier=None):
+
+ def __wait_for_launcher_editor(self, process,
+ old_path, new_path, identifier):
+- if process.poll() != None:
++ if process is None or process.poll() != None:
+ # Launcher editor closed.
+ if os.path.isfile(new_path):
+ # Update desktop_entry.
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index ed05d60..4aa5ce9 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -813,6 +813,9 @@ def __menu_edit_launcher(self, widget=None, event=None):
+ path = ""
+ self.dockbar_r().edit_launcher(path, self.identifier)
+ self.popup.hide()
++ if self.globals.gtkmenu:
++ # the modal DesktopFileEditor dialog prevented us from receiving selection-done signal
++ self.__menu_closed()
+
+ def __menu_pin(self, widget=None, event=None):
+ self.pinned = True
+
+From 1d31bdad9fe66119077d567fd09d376e3dfc285f Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 23 Aug 2020 20:42:28 +0800
+Subject: [PATCH 09/21] avoid calling method on None
+
+---
+ dockbarx/groupbutton.py | 7 ++++---
+ 1 file changed, 4 insertions(+), 3 deletions(-)
+
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index 4aa5ce9..5a3f48c 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -785,9 +785,10 @@ def __menu_position(self, menu):
+
+ def __menu_closed(self, menushell=None):
+ # Used only with the gtk menu
+- self.globals.gtkmenu = None
+- self.menu.delete_menu()
+- self.menu = None
++ if self.globals.gtkmenu:
++ self.globals.gtkmenu = None
++ self.menu.delete_menu()
++ self.menu = None
+
+ def __menu_unminimize_all_windows(self, widget=None, event=None):
+ if event:
+
+From 1dd377a77868b7100145e84001beb885d4867fd2 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 23 Aug 2020 22:42:26 +0800
+Subject: [PATCH 10/21] fixed method
+
+---
+ dockbarx/desktopfileeditor.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dockbarx/desktopfileeditor.py b/dockbarx/desktopfileeditor.py
+index 1f07531..3b11714 100644
+--- a/dockbarx/desktopfileeditor.py
++++ b/dockbarx/desktopfileeditor.py
+@@ -187,7 +187,7 @@ def load(self, filename):
+ self.terminal_check.set_active(self.get_item("Terminal") == "true")
+ icon = self.get_item("Icon")
+ if icon.startswith("/"):
+- pixbuf = GdkPixbuf.new_from_file_at_size(icon, 64, 64)
++ pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon, 64, 64)
+ else:
+ icon_theme = Gtk.IconTheme.get_default()
+ pixbuf = icon_theme.load_icon(icon, 64, 0)
+
+From fba486ec131e3356ff0236724eba92f57fb8c48e Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 23 Aug 2020 22:46:52 +0800
+Subject: [PATCH 11/21] fixed unhidden popup menu
+
+---
+ dockbarx/groupbutton.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index 5a3f48c..259c0b9 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -812,8 +812,8 @@ def __menu_edit_launcher(self, widget=None, event=None):
+ path = self.desktop_entry.getFileName()
+ else:
+ path = ""
+- self.dockbar_r().edit_launcher(path, self.identifier)
+ self.popup.hide()
++ self.dockbar_r().edit_launcher(path, self.identifier)
+ if self.globals.gtkmenu:
+ # the modal DesktopFileEditor dialog prevented us from receiving selection-done signal
+ self.__menu_closed()
+
+From 087436756a59c96c46b2b661d01fe1a7c12d7ba9 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 29 Aug 2020 00:31:50 +0800
+Subject: [PATCH 12/21] free pixmap to avoid memory leak. fixed issue #147
+
+---
+ dockbarx/windowbutton.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/dockbarx/windowbutton.py b/dockbarx/windowbutton.py
+index 751a15a..4226008 100644
+--- a/dockbarx/windowbutton.py
++++ b/dockbarx/windowbutton.py
+@@ -522,6 +522,7 @@ def set_preview_image(self):
+ geo = xwin.get_geometry()
+ pixmap = xwin.composite_name_window_pixmap()
+ image_object = pixmap.get_image(0, 0, geo.width, geo.height, Xlib.X.ZPixmap, 0xffffffff)
++ pixmap.free()
+ xwin.composite_unredirect_window(Xlib.ext.composite.RedirectAutomatic)
+ except:
+ self.preview.set_from_pixbuf(window.wnck.get_icon())
+
+From 89c39a570559d2cc974ece26023a8c9f8d9b7ee1 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 29 Aug 2020 12:18:11 +0800
+Subject: [PATCH 13/21] fixed Spacer width
+
+---
+ dockx | 7 -------
+ 1 file changed, 7 deletions(-)
+
+diff --git a/dockx b/dockx
+index af986a3..0349867 100755
+--- a/dockx
++++ b/dockx
+@@ -741,13 +741,6 @@ class DockX(CairoDockX):
+ self.box.pack_start(boxes[0], True, True, 0)
+ self.box.pack_start(boxes[1], False, False, 0)
+ self.box.pack_start(boxes[2], True, True, 0)
+- # Use hugh size requests so that the boxes get equally large.
+- if pos in ("left", "right"):
+- boxes[0].set_size_request(-1, 3000)
+- boxes[2].set_size_request(-1, 3000)
+- else:
+- boxes[0].set_size_request(3000, -1)
+- boxes[2].set_size_request(3000, -1)
+ else:
+ boxes = None
+ return boxes
+
+From 5281e9e3932cb67a05c2a5ee0386340f70af0757 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 29 Aug 2020 12:52:52 +0800
+Subject: [PATCH 14/21] catch exceptions during applet initialization
+
+---
+ dockx | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/dockx b/dockx
+index 0349867..97b79b7 100755
+--- a/dockx
++++ b/dockx
+@@ -715,7 +715,11 @@ class DockX(CairoDockX):
+ if appletscr is None:
+ continue
+ applet_id = self.applets.get_id(name)
+- applet = appletscr.get_dbx_applet({"name":name, "id":applet_id, "dock":self})
++ try:
++ applet = appletscr.get_dbx_applet({"name":name, "id":applet_id, "dock":self})
++ except:
++ logger.warning("Failed to load applet %s. If this applet was installed after DockbarX started, please restart DockbarX" % name)
++ continue
+ expand = applet.get_expand() and panel
+ applet.finish_init()
+ if expand:
+
+From d2044ece46db849d29061980367876acfb21abef Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 29 Aug 2020 13:42:46 +0800
+Subject: [PATCH 15/21] update AUR urls
+
+---
+ README.md | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/README.md b/README.md
+index d428506..696241c 100644
+--- a/README.md
++++ b/README.md
+@@ -33,13 +33,13 @@ sudo apt-get install dockbarx-themes-extra
+ ```
+
+ ## Install in archlinux
+-There is an aur for DockbarX
++There is an AUR for DockbarX
+
+-https://aur.archlinux.org/packages/dockbarx-gtk3-git/
++https://aur.archlinux.org/packages/dockbarx/
+
+ And there is also one for xfce4-dockbarx-plugin
+
+-https://aur.archlinux.org/packages/xfce4-dockbarx-plugin-gtk3-git/
++https://aur.archlinux.org/packages/xfce4-dockbarx-plugin/
+
+
+ ## Manual Installation
+
+From c7d87cf7c5be785441f7b43cc88e0b12ac0ceb6b Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 5 Sep 2020 01:29:59 +0800
+Subject: [PATCH 16/21] fixed a constant
+
+---
+ dockbarx/groupbutton.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index 259c0b9..e615ade 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -51,7 +51,7 @@
+ X = None
+
+ try:
+- WNCK_WINDOW_ACTION_MAXIMIZE = Wnck.WindowType.ACTION_MAXIMIZE
++ WNCK_WINDOW_ACTION_MAXIMIZE = Wnck.WindowActions.MAXIMIZE
+ except:
+ WNCK_WINDOW_ACTION_MAXIMIZE = 1 << 14
+
+
+From e61886994937e412fc112add644a1805aadd83d7 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sat, 5 Sep 2020 01:37:56 +0800
+Subject: [PATCH 17/21] fixed maximize menu status
+
+---
+ dockbarx/groupbutton.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index e615ade..e5b4440 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -613,6 +613,7 @@ def __menu_build(self):
+ if not window.wnck.is_maximized() \
+ and window.wnck.get_actions() & WNCK_WINDOW_ACTION_MAXIMIZE:
+ maximize = True
++ break
+ else:
+ maximize = False
+ minimize = self.get_unminimized_count() > 0
+
+From aec0ab5f463e8fa2f25d15ef0325cd95fddb413a Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 6 Sep 2020 12:20:52 +0800
+Subject: [PATCH 18/21] update dockx colors immediately when setting changed
+
+---
+ dockbarx/common.py | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/dockbarx/common.py b/dockbarx/common.py
+index 2686795..cd60d07 100644
+--- a/dockbarx/common.py
++++ b/dockbarx/common.py
+@@ -922,6 +922,7 @@ def __on_theme_gsettings_changed(self, settings, gkey, data=None):
+ def __on_dock_theme_gsettings_changed(self, settings, gkey, data=None):
+ colors = self.dock_theme_gsettings.get_value("colors").unpack()
+ self.__update_dock_colors(colors)
++ self.emit("dock-theme-changed")
+
+ def __get_settings(self, default):
+ settings = default.copy()
+
+From dbbae3a3ccea230819f5903954792ee64ed8cf88 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 6 Sep 2020 12:31:19 +0800
+Subject: [PATCH 19/21] fixed the initial visibility of some widgets
+
+---
+ dbx_preference | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/dbx_preference b/dbx_preference
+index cdeb1b2..2356bac 100755
+--- a/dbx_preference
++++ b/dbx_preference
+@@ -1433,9 +1433,9 @@ class PrefDialog():
+ notebook.append_page(plugins_box, Gtk.Label(label=_("Plugins")))
+ notebook.append_page(advanced_box, Gtk.Label(label=_("Advanced")))
+ ca.pack_start(notebook, True, True, 0)
+- self.__update()
+ self.dialog.add_button(_("_Close"), Gtk.ResponseType.CLOSE)
+ self.dialog.show_all()
++ self.__update()
+ dock = False
+ for name in dbus.SessionBus().list_names():
+ if str(name).startswith("org.dockbar.DockX"):
+
+From 32b36016a6a3019c6694893cfe0b2d19bed65774 Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 6 Sep 2020 19:55:38 +0800
+Subject: [PATCH 20/21] fixed drag & drop bugs
+
+---
+ dockbarx/dockbar.py | 71 ++++++++++++++--------
+ dockbarx/groupbutton.py | 130 ++++++++++++++++++++--------------------
+ dockx | 61 ++++++++++++-------
+ 3 files changed, 152 insertions(+), 110 deletions(-)
+
+diff --git a/dockbarx/dockbar.py b/dockbarx/dockbar.py
+index f6ec793..600df50 100644
+--- a/dockbarx/dockbar.py
++++ b/dockbarx/dockbar.py
+@@ -92,6 +92,8 @@ def __init__(self, dockbar):
+
+ self.drag_dest_set(0, [], 0)
+ self.drag_entered = False
++ self.drag_launcher = False
++ self.on_drop = False
+ self.connect("button-release-event", self.on_button_release_event)
+ self.connect("drag-motion", self.on_drag_motion)
+ self.connect("drag-leave", self.on_drag_leave)
+@@ -106,49 +108,65 @@ def on_button_release_event(self, widget, event):
+ def on_drag_drop(self, widget, drag_context, x, y, t):
+ targets = [target.name() for target in drag_context.list_targets()]
+ if "text/groupbutton_name" in targets:
+- self.drag_get_data(drag_context, "text/groupbutton_name", t)
+- drag_context.finish(True, False, t)
++ self.drag_get_data(drag_context, Gdk.Atom.intern("text/groupbutton_name", False), t)
+ elif "text/uri-list" in targets:
+- self.drag_get_data(drag_context, "text/uri-list", t)
+- drag_context.finish(True, False, t)
++ self.on_drop = True
++ self.drag_get_data(drag_context, Gdk.Atom.intern("text/uri-list", False), t)
+ else:
+- drag_context.finish(False, False, t)
++ return False
+ return True
+
+- def on_drag_data_received(self, widget, context, x, y, selection, targetType, t):
++ def on_drag_data_received(self, widget, drag_context, x, y, selection, targetType, t):
+ selection_target = selection.get_target().name()
+ if selection_target == "text/groupbutton_name":
+- self.dockbar_r().groupbutton_moved(selection.get_data(),
+- "after")
++ name = selection.get_data().decode()
++ self.dockbar_r().groupbutton_moved(name, "after")
++ drag_context.finish(True, False, t)
+ elif selection_target == "text/uri-list":
+- if ".desktop" in selection.get_data():
+- # .desktop file! This is a potential launcher.
+- #remove "file://" and "/n" from the URI
+- path = selection.get_data()
+- path = path.replace('\000', '') # for spacefm
+- if path.startswith("file://"):
+- path = path[7:]
+- else:
+- # No support for other kind of uris.
+- return
+- path = path.rstrip()
+- path = urllib.parse.unquote(path)
+- self.dockbar_r().launcher_dropped(path, "after")
++ dropped = False;
++ for uri in selection.get_uris():
++ uri = uri.replace('\000', '') # for spacefm
++ if uri.startswith("file://") and uri.endswith(".desktop"):
++ # .desktop file! This is a potential launcher.
++ if self.on_drop:
++ #remove "file://" from the URI
++ path = uri[7:]
++ path = urllib.parse.unquote(path)
++ self.dockbar_r().launcher_dropped(path, "after")
++ dropped = True
++ else:
++ self.drag_launcher = True
++ break
++ if self.on_drop:
++ drag_context.finish(dropped, False, t)
++ else:
++ self.on_drag_motion(widget, drag_context, x, y, t)
+
+ def on_drag_motion(self, widget, drag_context, x, y, t):
+ if not self.drag_entered:
+ self.on_drag_enter(widget, drag_context, x, y, t)
++ return True
+ targets = [target.name() for target in drag_context.list_targets()]
+ if "text/groupbutton_name" in targets:
+ Gdk.drag_status(drag_context, Gdk.DragAction.MOVE, t)
+- elif "text/uri-list" in targets():
+- Gdk.drag_status(drag_context, Gdk.DragAction.COPY, t)
++ elif "text/uri-list" in targets:
++ if self.drag_launcher:
++ Gdk.drag_status(drag_context, Gdk.DragAction.COPY, t)
++ else:
++ Gdk.drag_status(drag_context, Gdk.DragAction.PRIVATE, t)
+ else:
+ Gdk.drag_status(drag_context, Gdk.DragAction.PRIVATE, t)
+ return True
+
+ def on_drag_enter(self, widget, drag_context, x, y, t):
+ self.drag_entered = True
++ targets = [target.name() for target in drag_context.list_targets()]
++ if "text/groupbutton_name" in targets:
++ pass
++ elif "text/uri-list" in targets:
++ self.on_drop = False
++ self.drag_launcher = False
++ self.drag_get_data(drag_context, Gdk.Atom.intern("text/uri-list", False), t)
+
+ def on_drag_leave(self, widget, drag_context, t):
+ self.drag_entered = False
+@@ -1059,8 +1077,11 @@ def __add_window(self, window):
+ return
+
+ def __remove_window(self, window):
+- identifier = self.windows[window]
+- group = self.groups[identifier]
++ try:
++ identifier = self.windows[window]
++ group = self.groups[identifier]
++ except KeyError:
++ return
+ group.del_window(window)
+ if not len(group) and not group.pinned:
+ self.remove_groupbutton(group)
+diff --git a/dockbarx/groupbutton.py b/dockbarx/groupbutton.py
+index e5b4440..2cb5ad0 100644
+--- a/dockbarx/groupbutton.py
++++ b/dockbarx/groupbutton.py
+@@ -1391,10 +1391,10 @@ def __init__(self, group, size):
+ # raised.
+ self.drag_dest_set(0, [], 0)
+ self.drag_entered = False
+- self.launcher_drag = False
++ self.dnd_has_launcher = False
++ self.dnd_on_drop = False
+ self.dnd_position = "end"
+ self.dnd_show_popup = None
+- self.dd_uri = None
+
+ #Make buttons drag-able
+ self.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,[], Gdk.DragAction.MOVE)
+@@ -1486,13 +1486,13 @@ def update_state(self, force_update=False):
+ state_type = state_type | IconFactory.MOUSE_BUTTON_DOWN
+
+ if self.mouse_over or \
+- (self.drag_entered and not self.launcher_drag):
++ (self.drag_entered and not self.dnd_has_launcher):
+ state_type = state_type | IconFactory.MOUSE_OVER
+
+ if self.launch_effect:
+ state_type = state_type | IconFactory.LAUNCH_EFFECT
+
+- if self.launcher_drag:
++ if self.dnd_has_launcher:
+ if self.dnd_position == "start":
+ state_type = state_type | IconFactory.DRAG_DROPP_START
+ else:
+@@ -1720,6 +1720,7 @@ def on_drag_data_get(self, widget, context, selection, targetType, eventTime):
+
+ def on_drag_end(self, widget, drag_context, result=None):
+ self.is_current_drag_source = False
++ self.globals.dragging = False
+ #~ # A delay is needed to make sure the button is
+ #~ # shown after on_drag_end has hidden it and
+ #~ # not the other way around.
+@@ -1727,63 +1728,50 @@ def on_drag_end(self, widget, drag_context, result=None):
+
+ #### DnD (target)
+ def on_drag_drop(self, widget, drag_context, x, y, t):
+- group = self.group_r()
+ targets = [target.name() for target in drag_context.list_targets()]
+ if "text/groupbutton_name" in targets:
+ target_atom = Gdk.Atom.intern("text/groupbutton_name", False)
+ self.drag_get_data(drag_context, target_atom, t)
+- drag_context.finish(True, False, t)
+ elif "text/uri-list" in targets:
+- #Drag data should already be stored in self.dd_uri
+- if ".desktop" in self.dd_uri:
+- # .desktop file! This is a potential launcher.
+- #remove "file://" and "/n" from the URI
+- path = self.dd_uri
+- if path.startswith("file://"):
+- path = path.replace('\000', '') # for spacefm
+- path = path[7:]
+- else:
+- # No support for other kind of uris.
+- return
+- path = path.rstrip()
+- path = urllib.parse.unquote(path)
+- self.dockbar_r().launcher_dropped(path, group,
+- self.dnd_position)
+- else:
+- uri = self.dd_uri
+- # Remove the new line at the end
+- uri = uri.rstrip()
+- group.launch(None, None, uri)
+- drag_context.finish(True, False, t)
+- else:
+- drag_context.finish(False, False, t)
+- self.dd_uri = None
++ self.dnd_on_drop = True
++ target_atom = Gdk.Atom.intern("text/uri-list", False)
++ self.drag_get_data(drag_context, target_atom, t)
++ else:
++ return False
+ return True
+
+ def on_drag_data_received(self, widget, context, x, y, selection, targetType, t):
+ group = self.group_r()
+- name = group.identifier or group.desktop_entry.getFileName()
+ selection_target = selection.get_target().name()
+ if selection_target == "text/groupbutton_name":
++ name = group.identifier or group.desktop_entry.getFileName()
+ # Selection data is in bytes we need to decode it to a string.
+ data = selection.get_data().decode()
+ if data != name:
+ self.dockbar_r().groupbutton_moved(data, group, self.dnd_position)
++ context.finish(True, False, t)
+ elif selection_target == "text/uri-list":
+- # Uri lists are tested on first motion instead on drop
+- # to check if it's a launcher.
+- # The data is saved in self.dd_uri to be used again
+- # if the file is dropped.
+- self.dd_uri = selection.get_data().decode()
+- if ".desktop" in selection.get_data().decode():
+- # .desktop file! This is a potential launcher.
+- self.launcher_drag = True
+- self.update_state()
++ for uri in selection.get_uris():
++ uri = uri.replace('\000', '') # for spacefm
++ if uri.startswith("file://") and uri.endswith(".desktop"):
++ # .desktop file! This is a potential launcher.
++ if self.dnd_on_drop:
++ #remove "file://" from the URI
++ path = uri[7:]
++ path = urllib.parse.unquote(path)
++ self.dockbar_r().launcher_dropped(path, group,
++ self.dnd_position)
++ else:
++ self.dnd_has_launcher = True
++ break
++ elif self.dnd_on_drop:
++ group.launch(None, None, uri)
++ if self.dnd_on_drop:
++ context.finish(True, False, t)
++ else:
++ self.__update_dragging_status(x, y)
+
+ def on_drag_motion(self, widget, drag_context, x, y, t):
+- group = self.group_r()
+- if not self.drag_entered:
+- self.on_drag_enter(widget, drag_context, x, y, t)
+ targets = [target.name() for target in drag_context.list_targets()]
+ if "text/groupbutton_name" in targets and \
+ not self.is_current_drag_source:
+@@ -1792,38 +1780,26 @@ def on_drag_motion(self, widget, drag_context, x, y, t):
+ Gdk.drag_status(drag_context, Gdk.DragAction.COPY, t)
+ else:
+ Gdk.drag_status(drag_context, Gdk.DragAction.PRIVATE, t)
+- if self.launcher_drag:
+- dnd_position = "end"
+- if self.dockbar_r().orient in ("left", "right"):
+- if y <= self.get_allocation().height // 2:
+- dnd_position = "start"
+- else:
+- if x <= self.get_allocation().width // 2:
+- dnd_position = "start"
+- if dnd_position != self.dnd_position:
+- self.dnd_position = dnd_position
+- self.update_state()
++
++ if not self.drag_entered:
++ self.on_drag_enter(widget, drag_context, x, y, t)
++ return True
++
++ self.__update_dragging_status(x, y)
+ return True
+
+ def on_drag_enter(self, widget, drag_context, x, y, t):
+ group = self.group_r()
+ self.drag_entered = True
+ targets = [target.name() for target in drag_context.list_targets()]
+- if not "text/groupbutton_name" in targets:
+- win_nr = group.get_count()
+- if win_nr == 1:
+- group[0].select_after_delay(600)
+- elif win_nr > 1:
+- delay = self.globals.settings["popup_delay"]
+- self.dnd_show_popup = GLib.timeout_add(delay,
+- group.popup.show)
+ if "text/groupbutton_name" in targets:
+ if not self.is_current_drag_source:
+- self.launcher_drag = True
++ self.dnd_has_launcher = True
+ self.update_state()
+ elif "text/uri-list" in targets:
+ # We have to get the data to find out if this
+ # is a launcher or something else.
++ self.dnd_on_drop = False
+ target_atom = Gdk.Atom.intern("text/uri-list", False)
+ self.drag_get_data(drag_context, target_atom, t)
+ # No update_state() here!
+@@ -1832,7 +1808,7 @@ def on_drag_enter(self, widget, drag_context, x, y, t):
+
+ def on_drag_leave(self, widget, drag_context, t):
+ group = self.group_r()
+- self.launcher_drag = False
++ self.dnd_has_launcher = False
+ self.drag_entered = False
+ self.update_state()
+ group.popup.hide_if_not_hovered(100)
+@@ -1849,6 +1825,28 @@ def on_drag_leave(self, widget, drag_context, t):
+ #~ # the drop is completed.
+ #~ GLib.timeout_add(20, self.hide)
+
++ def __update_dragging_status(self, x, y):
++ if self.dnd_has_launcher:
++ dnd_position = "end"
++ if self.dockbar_r().orient in ("left", "right"):
++ if y <= self.get_allocation().height // 2:
++ dnd_position = "start"
++ else:
++ if x <= self.get_allocation().width // 2:
++ dnd_position = "start"
++ if dnd_position != self.dnd_position:
++ self.dnd_position = dnd_position
++ self.update_state()
++ else:
++ group = self.group_r()
++ win_nr = group.get_count()
++ if win_nr == 1:
++ group[0].select_after_delay(600)
++ elif win_nr > 1:
++ delay = self.globals.settings["popup_delay"]
++ self.dnd_show_popup = GLib.timeout_add(delay, group.popup.show)
++ self.update_state()
++
+
+ #### Events
+ def on_size_allocate(self, widget, allocation):
+@@ -1919,7 +1917,9 @@ def on_enter_notify_event(self, widget, event):
+ self.opacify(delay)
+
+ def on_leave_notify_event(self, widget, event):
+- self.leave_notify_sid = None
++ if self.leave_notify_sid is not None:
++ GLib.source_remove(self.leave_notify_sid)
++ self.leave_notify_sid = None
+ group = self.group_r()
+ if group is None:
+ return
+diff --git a/dockx b/dockx
+index 97b79b7..2610b2c 100755
+--- a/dockx
++++ b/dockx
+@@ -46,6 +46,7 @@ import weakref
+ import time
+ import dbus
+ import dbus.service
++import urllib.parse
+
+ WNCK_WINDOW_STATE_MINIMIZED = 1
+
+@@ -1361,6 +1362,8 @@ class EventPadding(Gtk.EventBox):
+ self.position = None
+ self.drag_dest_set(0, [], 0)
+ self.drag_entered = False
++ self.drag_launcher = False
++ self.on_drop = False
+ self.connect("button-release-event", self.on_button_release_event)
+ self.connect("drag-motion", self.on_drag_motion)
+ self.connect("drag-leave", self.on_drag_leave)
+@@ -1403,13 +1406,12 @@ class EventPadding(Gtk.EventBox):
+ def on_drag_drop(self, widget, drag_context, x, y, t):
+ targets = [target.name() for target in drag_context.list_targets()]
+ if "text/groupbutton_name" in targets:
+- self.drag_get_data(drag_context, "text/groupbutton_name", t)
+- drag_context.finish(True, False, t)
++ self.drag_get_data(drag_context, Gdk.Atom.intern("text/groupbutton_name", False), t)
+ elif "text/uri-list" in targets:
+- self.drag_get_data(drag_context, "text/uri-list", t)
+- drag_context.finish(True, False, t)
++ self.on_drop = True
++ self.drag_get_data(drag_context, Gdk.Atom.intern("text/uri-list", False), t)
+ else:
+- drag_context.finish(False, False, t)
++ return False
+ return True
+
+ def on_drag_data_received(self, widget, context, x, y, selection, targetType, t):
+@@ -1417,38 +1419,57 @@ class EventPadding(Gtk.EventBox):
+ return
+ selection_target = selection.get_target().name()
+ if selection_target == "text/groupbutton_name":
+- self.dock_r().dockbar.groupbutton_moved(selection.get_data(),
+- self.position)
++ data = selection.get_data().decode()
++ self.dock_r().dockbar.groupbutton_moved(data, self.position)
++ context.finish(True, False, t)
+ elif selection_target == "text/uri-list":
+- if ".desktop" in selection.get_data():
+- # .desktop file! This is a potential launcher.
+- #remove "file://" and "/n" from the URI
+- path = selection.get_data()
+- if path.startswith("file://"):
+- path = path[7:]
+- else:
+- # No support for other kind of uris.
+- return
+- path = path.rstrip()
+- path = path.replace("%20"," ")
+- self.dock_r().dockbar.launcher_dropped(path, self.position)
++ dropped = False
++ for uri in selection.get_uris():
++ uri = uri.replace('\000', '') # for spacefm
++ if uri.startswith("file://") and uri.endswith(".desktop"):
++ # .desktop file! This is a potential launcher.
++ if self.on_drop:
++ #remove "file://" from the URI
++ path = uri[7:]
++ path = urllib.parse.unquote(path)
++ self.dock_r().dockbar.launcher_dropped(path, self.position)
++ dropped = True
++ else:
++ self.drag_launcher = True
++ break
++ if self.on_drop:
++ context.finish(dropped, False, t)
++ else:
++ self.on_drag_motion(widget, context, x, y, t)
+
+ def on_drag_motion(self, widget, drag_context, x, y, t):
+ if not self.position in ("before", "after"):
+ return True
+ if not self.drag_entered:
+ self.on_drag_enter(widget, drag_context, x, y, t)
++ return True
+ targets = [target.name() for target in drag_context.list_targets()]
+ if "text/groupbutton_name" in targets:
+ Gdk.drag_status(drag_context, Gdk.DragAction.MOVE, t)
+ elif "text/uri-list" in targets:
+- Gdk.drag_status(drag_context, Gdk.DragAction.COPY, t)
++ if self.drag_launcher:
++ Gdk.drag_status(drag_context, Gdk.DragAction.COPY, t)
++ else:
++ Gdk.drag_status(drag_context, Gdk.DragAction.PRIVATE, t)
+ else:
+ Gdk.drag_status(drag_context, Gdk.DragAction.PRIVATE, t)
+ return True
+
+ def on_drag_enter(self, widget, drag_context, x, y, t):
+ self.drag_entered = True
++ targets = [target.name() for target in drag_context.list_targets()]
++ if "text/groupbutton_name" in targets:
++ pass
++ elif "text/uri-list" in targets:
++ self.on_drop = False
++ self.drag_launcher = False
++ self.drag_get_data(drag_context, Gdk.Atom.intern("text/uri-list", False), t)
++
+
+ def on_drag_leave(self, widget, drag_context, t):
+ self.drag_entered = False
+
+From 663001e6859f3181295845da45ac6b0c262a459d Mon Sep 17 00:00:00 2001
+From: xuzhen <xuzhen@users.noreply.github.com>
+Date: Sun, 6 Sep 2020 20:11:29 +0800
+Subject: [PATCH 21/21] sync settings
+
+---
+ dockbarx/common.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/dockbarx/common.py b/dockbarx/common.py
+index cd60d07..bf22b39 100644
+--- a/dockbarx/common.py
++++ b/dockbarx/common.py
+@@ -999,8 +999,10 @@ def update_colors(self, theme_name, theme_colors={}, theme_alphas={}):
+ alpha = self.theme_gsettings.get_value(a)
+ if self.theme_gsettings.get_user_value(c) is None:
+ self.theme_gsettings.set_value(c, color)
++ self.theme_gsettings.sync()
+ if self.theme_gsettings.get_user_value(a) is None:
+ self.theme_gsettings.set_value(a, alpha)
++ self.theme_gsettings.sync()
+
+ def update_popup_style(self, default_style):
+ # Runs when the theme has changed.
+@@ -1061,6 +1063,7 @@ def __update_dock_colors(self, colors):
+ if update_needed:
+ self.old_dock_gs_colors = colors
+ self.dock_theme_gsettings.set_value("colors", GLib.Variant("a{ss}", colors))
++ self.dock_theme_gsettings.sync()
+ self.emit("preference-update")
+
+
+@@ -1071,6 +1074,7 @@ def get_pinned_apps_list(self):
+
+ def set_pinned_apps_list(self, pinned_apps):
+ self.gsettings.set_value("launchers", GLib.Variant("as", pinned_apps))
++ self.gsettings.sync()
+
+ def set_shown_popup(self, popup):
+ if popup is None:
+@@ -1101,6 +1105,7 @@ def get_compiz_version(self):
+
+ def set_applets_enabled_list(self, applets_list):
+ self.applets_gsettings.set_value("enabled-list", GLib.Variant("as", applets_list))
++ self.applets_gsettings.sync()
+
+
+ __connector = Connector()