From 8121976af72541ab84e4703e5e8fd7f67011d26f Mon Sep 17 00:00:00 2001 From: Nico Rittstieg Date: Thu, 3 Jan 2019 16:29:15 +0100 Subject: [PATCH] [T] gtk3 gui based on gobject --> removed yad/zenity dependency --- README.md | 8 +- savedesktop/check.py | 26 ------ savedesktop/gui.py | 193 +++++++++++++++++++++++++++++------------- savedesktop/rvd.py | 9 +- savedesktop/svd.py | 2 +- savedesktop/wmctrl.py | 2 + setup.py | 6 +- 7 files changed, 150 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 9cd7061..cd80ed9 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,10 @@ Dependencies: - wmctrl (https://sites.google.com/site/tstyblo/wmctrl/) - xwininfo (for getting window geometry and extents) - xdotool (for hiding windows) - - Python 3 + - Python 3.7 - python-setuptools - - zenity, yad (for gtk dialogs) + - python-gobject + - gtk3 Compatibility: -------------------------- @@ -131,6 +132,7 @@ Profile files are stored in '~/.config/savedesktop' "y": 219, "width": 1094, "height": 599, + "timeout": 5, "cmd": [ "nemo" ], @@ -144,7 +146,7 @@ The following state properties are supported: - maximized_horz - shaded - hidden -- fullscreen. +- fullscreen Project Web site: -------------------- diff --git a/savedesktop/check.py b/savedesktop/check.py index f09fe99..4f1f0e8 100644 --- a/savedesktop/check.py +++ b/savedesktop/check.py @@ -42,16 +42,6 @@ def check_dependencies(args: argparse.Namespace): if args.gui: gui.show_error("xdotool is not installed") exit(-1) - if args.gui and not check_yad_installation(): - print("yad is not installed", file=sys.stderr) - if args.gui: - gui.show_error("yad is not installed") - exit(-1) - if args.gui and not check_zenity_installation(): - print("zenity is not installed", file=sys.stderr) - if args.gui: - gui.show_error("zenity is not installed") - exit(-1) def check_wmctrl_installation(): @@ -78,22 +68,6 @@ def check_xdotool_installation(): return False -def check_zenity_installation(): - try: - subprocess.call(["zenity", "--version"], stdout=subprocess.DEVNULL) - return True - except FileNotFoundError as e: - return False - - -def check_yad_installation(): - try: - subprocess.call(["yad", "--version"], stdout=subprocess.DEVNULL) - return True - except FileNotFoundError as e: - return False - - def check_desktop(args: argparse.Namespace, desktop_count: int): if args.desktop is not None: if args.desktop < 0 or args.desktop >= desktop_count: diff --git a/savedesktop/gui.py b/savedesktop/gui.py index 53c9368..9107546 100644 --- a/savedesktop/gui.py +++ b/savedesktop/gui.py @@ -18,18 +18,25 @@ # You should have received a copy of the GNU General Public License along with this program. # If not, see . +import gi + +gi.require_version('Gtk', '3.0') import argparse -import subprocess -import time from typing import List +from gi.repository import Gtk def show_error(msg: str): - subprocess.check_output(["zenity", - "--error", - "--title=Save Virtual Desktop", - "--width=600", - "--text={}".format(msg)]) + dialog = Gtk.MessageDialog(None, + 0, + Gtk.MessageType.ERROR, + Gtk.ButtonsType.CLOSE, + "An error has occurred") + dialog.format_secondary_text(msg) + dialog.set_title("Save/Restore Virtual Desktop") + dialog.set_default_size(500, 160) + dialog.run() + dialog.destroy() def show_save_desktop(args: argparse.Namespace, desktop_list: List[str], profile_list: List[str]): @@ -37,63 +44,129 @@ def show_save_desktop(args: argparse.Namespace, desktop_list: List[str], profile profile_list.append("default") if args.profile not in profile_list: profile_list.insert(0, args.profile) - else: - for i in range(len(profile_list)): - if profile_list[i] == args.profile: - profile_list[i] = "^{}".format(args.profile) - break - if args.desktop is not None: - desktop_list[args.desktop] = "^{}".format(desktop_list[args.desktop]) - try: - output = subprocess.check_output(["yad", - "--width=300", - "--form", - "--title=Save Virtual Desktop", - "--field=Desktop:CB", - "!".join(desktop_list), - "--field=Profile:CBE", - "!".join(profile_list), - "--field=Open JSON file:CHK", - "TRUE" if args.open else "FALSE", - ], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - # Return Code 1 -> The user has pressed Cancel button + + dialog = SaveDialog(args.desktop, desktop_list, args.profile, profile_list) + response = dialog.run() + + if response != Gtk.ResponseType.OK: + dialog.destroy() exit(0) - # parse yad output : b'0 - Workspace 1|default|TRUE|\n' - values = str(output)[2:-4].split("|") - args.desktop = int(values[0][0]) - args.profile = values[1].strip() + + args.desktop = dialog.desktop_combo.get_active() + args.profile = dialog.profile_combo.get_active_text().strip() if len(args.profile) == 0: args.profile = "default" - args.open = "TRUE" == values[2] - # avoid wmctrl timing probs - time.sleep(.100) + args.open = dialog.open_checkbox.get_active() + + dialog.destroy() def show_restore_desktop(args: argparse.Namespace, desktop_list: List[str], profile_list: List[str]): - for i in range(len(profile_list)): - if profile_list[i] == args.profile: - profile_list[i] = "^{}".format(args.profile) - break - desktop_list[args.desktop] = "^{}".format(desktop_list[args.desktop]) - try: - output = subprocess.check_output(["yad", - "--width=300", - "--form", - "--title=Restore Virtual Desktop", - "--field=Desktop:CB", - "!".join(desktop_list), - "--field=Profile:CB", - "!".join(profile_list), - ], - stderr=subprocess.STDOUT) - except subprocess.CalledProcessError: - # Return Code 1 -> The user has pressed Cancel button + dialog = RestoreDialog(args.desktop, desktop_list, args.profile, profile_list) + response = dialog.run() + + if response != Gtk.ResponseType.OK: + dialog.destroy() exit(0) - # parse yad output : b'0 - Workspace 1|default|\n' - values = str(output)[2:-4].split("|") - args.desktop = int(values[0][0]) - args.profile = values[1].strip() - # avoid wmctrl timing probs - time.sleep(.100) + + args.desktop = dialog.desktop_combo.get_active() + args.profile = dialog.profile_combo.get_active_text() + + dialog.destroy() + + +class SaveDialog(Gtk.Dialog): + + def __init__(self, current_desktop: int, desktop_list: List[str], current_profile, profile_list: List[str]): + Gtk.Dialog.__init__(self, + "Save Virtual Desktop", + None, 0, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, Gtk.ResponseType.OK)) + + self.set_border_width(5) + + self.set_icon(self.render_icon(Gtk.STOCK_DIALOG_QUESTION, Gtk.IconSize.DIALOG)) + + vbox = self.get_content_area() + vbox.set_spacing(5) + + hbox = Gtk.HBox(spacing=20) + vbox.pack_start(hbox, True, True, 0) + label = Gtk.Label("Desktop:") + label.set_size_request(70, 20) + label.set_xalign(0) + hbox.pack_start(label, False, False, 0) + self.desktop_combo = Gtk.ComboBoxText() + for desktop in desktop_list: + self.desktop_combo.append_text(desktop) + self.desktop_combo.set_active(current_desktop) + hbox.pack_start(self.desktop_combo, True, True, 0) + + hbox = Gtk.HBox(spacing=20) + vbox.pack_start(hbox, True, True, 0) + label = Gtk.Label("Profile:") + label.set_size_request(70, 20) + label.set_xalign(0) + hbox.pack_start(label, False, False, 0) + self.profile_combo = Gtk.ComboBoxText() + for profile in profile_list: + self.profile_combo.append_text(profile) + self.profile_combo.set_active(profile_list.index(current_profile)) + hbox.pack_start(self.profile_combo, True, True, 0) + + hbox = Gtk.HBox(spacing=10) + vbox.pack_start(hbox, True, True, 10) + label = Gtk.Label() + label.set_size_request(70, 20) + hbox.pack_start(label, False, False, 0) + self.open_checkbox = Gtk.CheckButton("Open JSON file") + hbox.pack_start(self.open_checkbox, False, False, 0) + + vbox.pack_start(Gtk.HSeparator(), True, True, 0) + + self.show_all() + + +class RestoreDialog(Gtk.Dialog): + + def __init__(self, current_desktop: int, desktop_list: List[str], current_profile, profile_list: List[str]): + Gtk.Dialog.__init__(self, + "Restore Virtual Desktop", + None, 0, + (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, + Gtk.STOCK_OK, Gtk.ResponseType.OK)) + + self.set_border_width(5) + self.set_icon(self.render_icon(Gtk.STOCK_DIALOG_QUESTION, Gtk.IconSize.DIALOG)) + + vbox = self.get_content_area() + vbox.set_spacing(5) + + hbox = Gtk.HBox(spacing=20) + vbox.pack_start(hbox, True, True, 0) + label = Gtk.Label("Desktop:") + label.set_size_request(70, 20) + label.set_xalign(0) + hbox.pack_start(label, False, False, 0) + self.desktop_combo = Gtk.ComboBoxText() + for desktop in desktop_list: + self.desktop_combo.append_text(desktop) + self.desktop_combo.set_active(current_desktop) + hbox.pack_start(self.desktop_combo, True, True, 0) + + hbox = Gtk.HBox(spacing=20) + vbox.pack_start(hbox, True, True, 10) + label = Gtk.Label("Profile:") + label.set_size_request(70, 20) + label.set_xalign(0) + hbox.pack_start(label, False, False, 0) + self.profile_combo = Gtk.ComboBoxText.new_with_entry() + for profile in profile_list: + self.profile_combo.append_text(profile) + self.profile_combo.set_active(profile_list.index(current_profile)) + hbox.pack_start(self.profile_combo, True, True, 0) + + vbox.pack_start(Gtk.HSeparator(), True, True, 0) + + self.show_all() diff --git a/savedesktop/rvd.py b/savedesktop/rvd.py index 6d73dd0..545c13c 100755 --- a/savedesktop/rvd.py +++ b/savedesktop/rvd.py @@ -35,8 +35,8 @@ def main(): parser = argparse.ArgumentParser(description='Restore virtual desktops (workspaces)') parser.add_argument("-g", "--gui", action="store_true", help="gui mode") parser.add_argument("-p", "--profile", default="default", help="profile name") - parser.add_argument("-d", "--desktop", type=int, default=0, help="target desktop number from 0 to n") - parser.add_argument("--version", action="version", version="0.1.0") + parser.add_argument("-d", "--desktop", type=int, default=None, help="target desktop number from 0 to n") + parser.add_argument("--version", action="version", version="0.1.1") args = parser.parse_args() c.check_dependencies(args) try: @@ -57,7 +57,10 @@ def restore(args: argparse.Namespace): try: window_list = p.read_profile(args.profile) except FileNotFoundError as e: - print("profile '{0}' not found".format(e.filename), file=sys.stderr) + msg = "profile '{0}' not found".format(e.filename) + print(msg, file=sys.stderr) + if args.gui: + gui.show_error(msg) exit(-1) wmctrl.switch_desktop(args.desktop) for props in window_list: diff --git a/savedesktop/svd.py b/savedesktop/svd.py index 577085b..8d242d6 100755 --- a/savedesktop/svd.py +++ b/savedesktop/svd.py @@ -34,7 +34,7 @@ def main(): parser.add_argument("-o", "--open", action="store_true", help="show saved profile") parser.add_argument("-p", "--profile", default="default", help="profile name") parser.add_argument("-d", "--desktop", type=int, default=None, help="desktop number from 0 to n") - parser.add_argument("--version", action="version", version="0.1.0") + parser.add_argument("--version", action="version", version="0.1.1") args = parser.parse_args() c.check_dependencies(args) try: diff --git a/savedesktop/wmctrl.py b/savedesktop/wmctrl.py index 33d2736..1ec9a6d 100644 --- a/savedesktop/wmctrl.py +++ b/savedesktop/wmctrl.py @@ -40,6 +40,8 @@ def list_window_details(desktop: int = None) -> List[dict]: result = list() out = run_wmctrl("-p", "-G", "-l") for line in out.split('\n'): + if line.endswith("Save Virtual Desktop"): + continue tokens = line.split(maxsplit=7) if desktop is not None and desktop != int(tokens[1]): continue diff --git a/setup.py b/setup.py index 2451eda..3aee4a6 100755 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ setuptools.setup( name='savedesktop', - version='0.1.0', + version='0.1.1', data_files=data_files, entry_points={ 'console_scripts': ['svd=savedesktop.svd:main', 'rvd=savedesktop.rvd:main'] @@ -40,10 +40,10 @@ description='cli script to save and restore virtual desktops', long_description=long_description, long_description_content_type='text/markdown', - url='https://gitlab.com/nrittsti/sd', + url='https://github.com/nrittsti/savedesktop', packages=setuptools.find_packages(), license="GPL", - keywords=['window manager', 'wmctrl', 'desktop'], + keywords=['window manager', 'wmctrl', 'desktop', 'automation'], classifiers=( 'Programming Language :: Python :: 3', 'License :: OSI Approved :: GPL License',