From 486cc6f502e35b958fcf531ba157c1c420e6c95c Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Sat, 9 Feb 2019 01:55:59 +0100 Subject: add nxdialog for displaying dialogs By adding NX_CLIENT= to the envrionment certain actions in nx will open dialogs. If the options string contains "menu=1" rootless windows will show a popup menu when the mouse hits the center of the title bar offering session disconnect/terminatation. --- nxdialog/nxdialog | 369 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100755 nxdialog/nxdialog diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog new file mode 100755 index 000000000..78fa62ae3 --- /dev/null +++ b/nxdialog/nxdialog @@ -0,0 +1,369 @@ +#!/usr/bin/env python2 +# + +# Copyright (C) 2008 Google Inc. +# Copyright (C) 2019 Ulrich Sibiller +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. + +# This version is based on the nxdialog.py of the long abandoned +# Google project "neatx" (https://code.google.com/archive/p/neatx/). +# List of changes: +# - pulled in the few parts of the neatx python modules that are actually +# to make it a standlone script +# - added usage output +# - dropped logging code, print errors to stderr +# - can handle the "yesno" dialog type +# - added missing docstrings +# - pylint improvements +# - removed neatx entry from the pulldoww menu + +"""nxdialog program for handling dialog display.""" + +# If an "NX_CLIENT" environment variable is not provided to nxagent +# nxcomp library assumes this script is located in /usr/NX/bin/nxclient + +import optparse +import os +import signal +import sys + +import gtk + +import pygtk +pygtk.require("2.0") + +PROGRAM = "nxdialog" + +DISCONNECT = 1 +TERMINATE = 2 + +EXIT_SUCCESS = 0 +EXIT_FAILURE = 1 + +CANCEL_TEXT = "Cancel" +DISCONNECT_TEXT = "Disconnect" +TERMINATE_TEXT = "Terminate" +YES_TEXT = "Yes" +NO_TEXT = "No" + +DLG_TYPE_ERROR = "error" +DLG_TYPE_OK = "ok" +DLG_TYPE_PANIC = "panic" +DLG_TYPE_PULLDOWN = "pulldown" +DLG_TYPE_QUIT = "quit" +DLG_TYPE_YESNO = "yesno" +DLG_TYPE_YESNOSUSPEND = "yesnosuspend" + +VALID_DLG_TYPES = frozenset([ + DLG_TYPE_ERROR, + DLG_TYPE_OK, + DLG_TYPE_PANIC, + DLG_TYPE_PULLDOWN, + DLG_TYPE_QUIT, + DLG_TYPE_YESNO, + DLG_TYPE_YESNOSUSPEND, + ]) + +class PullDownMenu(object): + """ Shows a popup menu to disconnect/terminate session. """ + + def __init__(self, window_id): + """ Initializes this class. + + @type window_id: int + @param window_id: X11 window id of target window + + """ + self._window_id = window_id + self._result = None + + def Show(self): + """ Shows popup and returns result. """ + + win = gtk.gdk.window_foreign_new(self._window_id) + + menu = gtk.Menu() + menu.connect("deactivate", self._MenuDeactivate) + + # TODO: Show title item in bold font + title = gtk.MenuItem(label="Session control") + title.set_sensitive(False) + menu.append(title) + + disconnect = gtk.MenuItem(label=DISCONNECT_TEXT) + disconnect.connect("activate", self._ItemActivate, DISCONNECT) + menu.append(disconnect) + + terminate = gtk.MenuItem(label=TERMINATE_TEXT) + terminate.connect("activate", self._ItemActivate, TERMINATE) + menu.append(terminate) + + menu.append(gtk.SeparatorMenuItem()) + + cancel = gtk.MenuItem(label=CANCEL_TEXT) + menu.append(cancel) + + menu.show_all() + + menu.popup(parent_menu_shell=None, parent_menu_item=None, + func=self._PosMenu, data=win, + button=0, activate_time=gtk.get_current_event_time()) + + gtk.main() + + return self._result + + def _ItemActivate(self, _, result): + """ called when a menu item is selected """ + self._result = result + gtk.main_quit() + + def _MenuDeactivate(self, _): + """ called when menu is deactivated """ + gtk.main_quit() + + def _PosMenu(self, menu, parent): + """ Positions menu at the top center of the parent window. """ + # Get parent geometry and origin + (_, _, win_width, _, _) = parent.get_geometry() + (win_x, win_y) = parent.get_origin() + + # Calculate width of menu + (menu_width, _) = menu.size_request() + + # Calculate center + x = win_x + ((win_width - menu_width) / 2) + + return (x, win_y, True) + + +def ShowYesNoSuspendBox(title, text): + """ Shows a message box to disconnect/terminate session. + + @type title: str + @param title: Message box title + @type text: str + @param text: Message box text + @return: Choosen action + + """ + dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) + dlg.set_title(title) + dlg.set_markup(text) + dlg.add_button(DISCONNECT_TEXT, DISCONNECT) + dlg.add_button(TERMINATE_TEXT, TERMINATE) + dlg.add_button(CANCEL_TEXT, gtk.RESPONSE_CANCEL) + + res = dlg.run() + + if res in (DISCONNECT, TERMINATE): + return res + + # Everything else is cancel + return None + + +def ShowYesNoBox(title, text): + """ Shows a message box with answers yes and no. + + @type title: str + @param title: Message box title + @type text: str + @param text: Message box text + @return: Choosen action + + """ + dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) + dlg.set_title(title) + dlg.set_markup(text) + dlg.add_button(YES_TEXT, TERMINATE) + dlg.add_button(NO_TEXT, gtk.RESPONSE_CANCEL) + + res = dlg.run() + + if res == TERMINATE: + return res + + # Everything else is cancel + return None + + +def HandleSessionAction(agentpid, action): + """ Execute session action choosen by user. + + @type agentpid: int + @param agentpid: Nxagent process id as passed by command line + @type action: int or None + @param action: Choosen action + + """ + + if action == DISCONNECT: + print "Disconnecting from session, sending SIGHUP to %s" % (agentpid) + os.kill(agentpid, signal.SIGHUP) + + elif action == TERMINATE: + print "Terminating session, sending SIGTERM to process %s" % (agentpid) + os.kill(agentpid, signal.SIGTERM) + + elif action is None: + pass + + else: + raise NotImplementedError() + + +def ShowSimpleMessageBox(icon, title, text): + """ Shows a simple message box. + + @type icon: QMessageBox.Icon + @param icon: Icon for message box + @type title: str + @param title: Message box title + @type text: str + @param text: Message box text + + """ + dlg = gtk.MessageDialog(type=icon, flags=gtk.DIALOG_MODAL, + buttons=gtk.BUTTONS_OK) + dlg.set_title(title) + dlg.set_markup(text) + dlg.run() + + +class NxDialogProgram(object): + """ the main program """ + def __init__(self): + self.args = None + self.options = None + + def Main(self): + """ let's do something """ + try: + (self.options, self.args) = self.ParseArgs() + + self.Run() + + except (SystemExit, KeyboardInterrupt): + raise + + except Exception, e: + sys.stderr.write("Caught exception: %s\n" % (e)) + sys.exit(EXIT_FAILURE) + + def ParseArgs(self): + """ init parser """ + parser = optparse.OptionParser(option_list=self.BuildOptions(), + formatter=optparse.TitledHelpFormatter()) + return parser.parse_args() + + def BuildOptions(self): + """ build options for the parser """ + return [ + # nxagent 3.5.99.18 only uses yesno, ok, pulldown and yesnosuspend + # yesno dialogs will always kill the session if "yes" is selected + optparse.make_option("--dialog", type="string", dest="dialog_type", + help='type of dialog to show, one of "yesno", \ + "ok", "error", "panic", "quit", "pulldown", \ + "yesnosuspend"'), + optparse.make_option("--message", type="string", dest="text", + help="message text to display in the dialog"), + optparse.make_option("--caption", type="string", dest="caption", + help="window title of the dialog"), + optparse.make_option("--display", type="string", dest="display", + help="X11 display where the dialog should be \ + shown"), + optparse.make_option("--parent", type="int", dest="agentpid", + help="pid of the nxagent"), + optparse.make_option("--window", type="int", dest="window", + help="id of window where to embed the \ + pulldown dialog type"), + # -class, -local, -allowmultiple are unused in nxlibs 3.5.99.18 + optparse.make_option("--class", type="string", dest="dlgclass", + default="info", + help="class of the message (info, warning, error) \ + default: info) [currently unimplemented]"), + optparse.make_option("--local", action="store_true", dest="local", + help="specify that proxy mode is used \ + [currently unimplemented]"), + optparse.make_option("--allowmultiple", action="store_true", + dest="allowmultiple", + help="allow launching more than one dialog with \ + the same message [currently unimplemented]"), + ] + + def Run(self): + """ Disconnect/terminate NX session upon user's request. """ + + if not self.options.dialog_type: + sys.stderr.write("Dialog type not supplied via --type\n") + sys.exit(EXIT_FAILURE) + + dlgtype = self.options.dialog_type + + if dlgtype not in VALID_DLG_TYPES: + sys.stderr.write("Invalid dialog type '%s'\n" % (dlgtype)) + sys.exit(EXIT_FAILURE) + + if dlgtype in (DLG_TYPE_PULLDOWN, + DLG_TYPE_YESNOSUSPEND, + DLG_TYPE_YESNO) and not self.options.agentpid: + sys.stderr.write("Agent pid not supplied via --parent\n") + sys.exit(EXIT_FAILURE) + + if dlgtype == DLG_TYPE_PULLDOWN and not self.options.window: + sys.stderr.write("Window id not supplied via --window\n") + sys.exit(EXIT_FAILURE) + + if self.options.caption: + message_caption = self.options.caption + else: + message_caption = sys.argv[0] + + if self.options.text: + message_text = self.options.text + else: + message_text = "" + + if self.options.display: + os.environ["DISPLAY"] = self.options.display + + if dlgtype == DLG_TYPE_OK: + ShowSimpleMessageBox(gtk.MESSAGE_INFO, message_caption, message_text) + + elif dlgtype in (DLG_TYPE_ERROR, DLG_TYPE_PANIC): + ShowSimpleMessageBox(gtk.MESSAGE_ERROR, message_caption, message_text) + + elif dlgtype == DLG_TYPE_PULLDOWN: + HandleSessionAction(self.options.agentpid, + PullDownMenu(self.options.window).Show()) + + elif dlgtype == DLG_TYPE_YESNOSUSPEND: + HandleSessionAction(self.options.agentpid, + ShowYesNoSuspendBox(message_caption, message_text)) + + elif dlgtype == DLG_TYPE_YESNO: + HandleSessionAction(self.options.agentpid, + ShowYesNoBox(message_caption, message_text)) + + else: + # TODO: Implement all dialog types + sys.stderr.write("Dialog type '%s' not implemented" % (dlgtype)) + sys.exit(EXIT_FAILURE) + + +NxDialogProgram().Main() -- cgit v1.2.3 From 2aa575f0db642fc9a5820488440a48f235d8a89c Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Thu, 28 Feb 2019 17:46:48 +0100 Subject: nxdialog: pylint improvements --- nxdialog/nxdialog | 449 +++++++++++++++++++++++++++--------------------------- 1 file changed, 228 insertions(+), 221 deletions(-) diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog index 78fa62ae3..f7c061e17 100755 --- a/nxdialog/nxdialog +++ b/nxdialog/nxdialog @@ -76,294 +76,301 @@ VALID_DLG_TYPES = frozenset([ DLG_TYPE_QUIT, DLG_TYPE_YESNO, DLG_TYPE_YESNOSUSPEND, - ]) +]) + class PullDownMenu(object): - """ Shows a popup menu to disconnect/terminate session. """ + """ Shows a popup menu to disconnect/terminate session. """ - def __init__(self, window_id): - """ Initializes this class. + def __init__(self, window_id): + """ Initializes this class. - @type window_id: int - @param window_id: X11 window id of target window + @type window_id: int + @param window_id: X11 window id of target window - """ - self._window_id = window_id - self._result = None + """ + self.window_id = window_id + self.result = None - def Show(self): - """ Shows popup and returns result. """ + def show(self): + """ Shows popup and returns result. """ - win = gtk.gdk.window_foreign_new(self._window_id) + win = gtk.gdk.window_foreign_new(self.window_id) - menu = gtk.Menu() - menu.connect("deactivate", self._MenuDeactivate) + menu = gtk.Menu() + menu.connect("deactivate", self.menu_deactivate) - # TODO: Show title item in bold font - title = gtk.MenuItem(label="Session control") - title.set_sensitive(False) - menu.append(title) + # TODO: Show title item in bold font + title = gtk.MenuItem(label="Session control") + title.set_sensitive(False) + menu.append(title) - disconnect = gtk.MenuItem(label=DISCONNECT_TEXT) - disconnect.connect("activate", self._ItemActivate, DISCONNECT) - menu.append(disconnect) + disconnect = gtk.MenuItem(label=DISCONNECT_TEXT) + disconnect.connect("activate", self.item_activate, DISCONNECT) + menu.append(disconnect) - terminate = gtk.MenuItem(label=TERMINATE_TEXT) - terminate.connect("activate", self._ItemActivate, TERMINATE) - menu.append(terminate) + terminate = gtk.MenuItem(label=TERMINATE_TEXT) + terminate.connect("activate", self.item_activate, TERMINATE) + menu.append(terminate) - menu.append(gtk.SeparatorMenuItem()) + menu.append(gtk.SeparatorMenuItem()) - cancel = gtk.MenuItem(label=CANCEL_TEXT) - menu.append(cancel) + cancel = gtk.MenuItem(label=CANCEL_TEXT) + menu.append(cancel) - menu.show_all() + menu.show_all() - menu.popup(parent_menu_shell=None, parent_menu_item=None, - func=self._PosMenu, data=win, - button=0, activate_time=gtk.get_current_event_time()) + menu.popup(parent_menu_shell=None, parent_menu_item=None, + func=self.pos_menu, data=win, + button=0, activate_time=gtk.get_current_event_time()) - gtk.main() + gtk.main() - return self._result + return self.result - def _ItemActivate(self, _, result): - """ called when a menu item is selected """ - self._result = result - gtk.main_quit() + def item_activate(self, _, result): + """ called when a menu item is selected """ + self.result = result + gtk.main_quit() - def _MenuDeactivate(self, _): - """ called when menu is deactivated """ - gtk.main_quit() + @staticmethod + def menu_deactivate(_): + """ called when menu is deactivated """ + gtk.main_quit() - def _PosMenu(self, menu, parent): - """ Positions menu at the top center of the parent window. """ - # Get parent geometry and origin - (_, _, win_width, _, _) = parent.get_geometry() - (win_x, win_y) = parent.get_origin() + @staticmethod + def pos_menu(menu, parent): + """ Positions menu at the top center of the parent window. """ + # Get parent geometry and origin + (_, _, win_width, _, _) = parent.get_geometry() + (win_x, win_y) = parent.get_origin() - # Calculate width of menu - (menu_width, _) = menu.size_request() + # Calculate width of menu + (menu_width, _) = menu.size_request() - # Calculate center - x = win_x + ((win_width - menu_width) / 2) + # Calculate center + center_x = win_x + ((win_width - menu_width) / 2) - return (x, win_y, True) + return (center_x, win_y, True) -def ShowYesNoSuspendBox(title, text): - """ Shows a message box to disconnect/terminate session. +def show_yes_no_suspend_box(title, text): + """ Shows a message box to disconnect/terminate session. - @type title: str - @param title: Message box title - @type text: str - @param text: Message box text - @return: Choosen action + @type title: str + @param title: Message box title + @type text: str + @param text: Message box text + @return: Choosen action - """ - dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) - dlg.set_title(title) - dlg.set_markup(text) - dlg.add_button(DISCONNECT_TEXT, DISCONNECT) - dlg.add_button(TERMINATE_TEXT, TERMINATE) - dlg.add_button(CANCEL_TEXT, gtk.RESPONSE_CANCEL) + """ + dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) + dlg.set_title(title) + dlg.set_markup(text) + dlg.add_button(DISCONNECT_TEXT, DISCONNECT) + dlg.add_button(TERMINATE_TEXT, TERMINATE) + dlg.add_button(CANCEL_TEXT, gtk.RESPONSE_CANCEL) - res = dlg.run() + res = dlg.run() - if res in (DISCONNECT, TERMINATE): - return res + if res in (DISCONNECT, TERMINATE): + return res - # Everything else is cancel - return None + # Everything else is cancel + return None -def ShowYesNoBox(title, text): - """ Shows a message box with answers yes and no. +def show_yes_no_box(title, text): + """ Shows a message box with answers yes and no. - @type title: str - @param title: Message box title - @type text: str - @param text: Message box text - @return: Choosen action + @type title: str + @param title: Message box title + @type text: str + @param text: Message box text + @return: Choosen action - """ - dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) - dlg.set_title(title) - dlg.set_markup(text) - dlg.add_button(YES_TEXT, TERMINATE) - dlg.add_button(NO_TEXT, gtk.RESPONSE_CANCEL) + """ + dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) + dlg.set_title(title) + dlg.set_markup(text) + dlg.add_button(YES_TEXT, TERMINATE) + dlg.add_button(NO_TEXT, gtk.RESPONSE_CANCEL) - res = dlg.run() + res = dlg.run() - if res == TERMINATE: - return res + if res == TERMINATE: + return res - # Everything else is cancel - return None + # Everything else is cancel + return None -def HandleSessionAction(agentpid, action): - """ Execute session action choosen by user. +def handle_session_action(agentpid, action): + """ Execute session action choosen by user. - @type agentpid: int - @param agentpid: Nxagent process id as passed by command line - @type action: int or None - @param action: Choosen action + @type agentpid: int + @param agentpid: Nxagent process id as passed by command line + @type action: int or None + @param action: Choosen action - """ + """ - if action == DISCONNECT: - print "Disconnecting from session, sending SIGHUP to %s" % (agentpid) - os.kill(agentpid, signal.SIGHUP) + if action == DISCONNECT: + print "Disconnecting from session, sending SIGHUP to %s" % (agentpid) + os.kill(agentpid, signal.SIGHUP) - elif action == TERMINATE: - print "Terminating session, sending SIGTERM to process %s" % (agentpid) - os.kill(agentpid, signal.SIGTERM) + elif action == TERMINATE: + print "Terminating session, sending SIGTERM to process %s" % (agentpid) + os.kill(agentpid, signal.SIGTERM) - elif action is None: - pass + elif action is None: + pass - else: - raise NotImplementedError() + else: + raise NotImplementedError() -def ShowSimpleMessageBox(icon, title, text): - """ Shows a simple message box. +def show_simple_message_box(icon, title, text): + """ Shows a simple message box. - @type icon: QMessageBox.Icon - @param icon: Icon for message box - @type title: str - @param title: Message box title - @type text: str - @param text: Message box text + @type icon: QMessageBox.Icon + @param icon: Icon for message box + @type title: str + @param title: Message box title + @type text: str + @param text: Message box text - """ - dlg = gtk.MessageDialog(type=icon, flags=gtk.DIALOG_MODAL, - buttons=gtk.BUTTONS_OK) - dlg.set_title(title) - dlg.set_markup(text) - dlg.run() + """ + dlg = gtk.MessageDialog(type=icon, flags=gtk.DIALOG_MODAL, + buttons=gtk.BUTTONS_OK) + dlg.set_title(title) + dlg.set_markup(text) + dlg.run() class NxDialogProgram(object): - """ the main program """ - def __init__(self): - self.args = None - self.options = None - - def Main(self): - """ let's do something """ - try: - (self.options, self.args) = self.ParseArgs() - - self.Run() - - except (SystemExit, KeyboardInterrupt): - raise - - except Exception, e: - sys.stderr.write("Caught exception: %s\n" % (e)) - sys.exit(EXIT_FAILURE) - - def ParseArgs(self): - """ init parser """ - parser = optparse.OptionParser(option_list=self.BuildOptions(), - formatter=optparse.TitledHelpFormatter()) - return parser.parse_args() - - def BuildOptions(self): - """ build options for the parser """ - return [ - # nxagent 3.5.99.18 only uses yesno, ok, pulldown and yesnosuspend - # yesno dialogs will always kill the session if "yes" is selected - optparse.make_option("--dialog", type="string", dest="dialog_type", - help='type of dialog to show, one of "yesno", \ - "ok", "error", "panic", "quit", "pulldown", \ - "yesnosuspend"'), - optparse.make_option("--message", type="string", dest="text", - help="message text to display in the dialog"), - optparse.make_option("--caption", type="string", dest="caption", - help="window title of the dialog"), - optparse.make_option("--display", type="string", dest="display", - help="X11 display where the dialog should be \ - shown"), - optparse.make_option("--parent", type="int", dest="agentpid", - help="pid of the nxagent"), - optparse.make_option("--window", type="int", dest="window", - help="id of window where to embed the \ - pulldown dialog type"), - # -class, -local, -allowmultiple are unused in nxlibs 3.5.99.18 - optparse.make_option("--class", type="string", dest="dlgclass", - default="info", - help="class of the message (info, warning, error) \ - default: info) [currently unimplemented]"), - optparse.make_option("--local", action="store_true", dest="local", - help="specify that proxy mode is used \ + """ the main program """ + + def __init__(self): + self.args = None + self.options = None + + def main(self): + """ let's do something """ + try: + (self.options, self.args) = self.parse_args() + + self.run() + + except (SystemExit, KeyboardInterrupt): + raise + + except Exception, expt: + sys.stderr.write("Caught exception: %s\n" % (expt)) + sys.exit(EXIT_FAILURE) + + def parse_args(self): + """ init parser """ + parser = optparse.OptionParser(option_list=self.build_options(), + formatter=optparse.TitledHelpFormatter()) + return parser.parse_args() + + @staticmethod + def build_options(): + """ build options for the parser """ + return [ + # nxagent 3.5.99.18 only uses yesno, ok, pulldown and yesnosuspend + # yesno dialogs will always kill the session if "yes" is selected + optparse.make_option("--dialog", type="string", dest="dialog_type", + help='type of dialog to show, one of "yesno", \ + "ok", "error", "panic", "quit", "pulldown", \ + "yesnosuspend"'), + optparse.make_option("--message", type="string", dest="text", + help="message text to display in the dialog"), + optparse.make_option("--caption", type="string", dest="caption", + help="window title of the dialog"), + optparse.make_option("--display", type="string", dest="display", + help="X11 display where the dialog should be \ + shown"), + optparse.make_option("--parent", type="int", dest="agentpid", + help="pid of the nxagent"), + optparse.make_option("--window", type="int", dest="window", + help="id of window where to embed the \ + pulldown dialog type"), + # -class, -local, -allowmultiple are unused in nxlibs 3.5.99.18 + optparse.make_option("--class", type="string", dest="dlgclass", + default="info", + help="class of the message (info, warning, error) \ + default: info) [currently unimplemented]"), + optparse.make_option("--local", action="store_true", dest="local", + help="specify that proxy mode is used \ [currently unimplemented]"), - optparse.make_option("--allowmultiple", action="store_true", - dest="allowmultiple", - help="allow launching more than one dialog with \ - the same message [currently unimplemented]"), - ] + optparse.make_option("--allowmultiple", action="store_true", + dest="allowmultiple", + help="allow launching more than one dialog with \ + the same message [currently unimplemented]"), + ] - def Run(self): - """ Disconnect/terminate NX session upon user's request. """ + def run(self): + """ Disconnect/terminate NX session upon user's request. """ - if not self.options.dialog_type: - sys.stderr.write("Dialog type not supplied via --type\n") - sys.exit(EXIT_FAILURE) + if not self.options.dialog_type: + sys.stderr.write("Dialog type not supplied via --type\n") + sys.exit(EXIT_FAILURE) - dlgtype = self.options.dialog_type + dlgtype = self.options.dialog_type - if dlgtype not in VALID_DLG_TYPES: - sys.stderr.write("Invalid dialog type '%s'\n" % (dlgtype)) - sys.exit(EXIT_FAILURE) + if dlgtype not in VALID_DLG_TYPES: + sys.stderr.write("Invalid dialog type '%s'\n" % (dlgtype)) + sys.exit(EXIT_FAILURE) - if dlgtype in (DLG_TYPE_PULLDOWN, - DLG_TYPE_YESNOSUSPEND, - DLG_TYPE_YESNO) and not self.options.agentpid: - sys.stderr.write("Agent pid not supplied via --parent\n") - sys.exit(EXIT_FAILURE) + if dlgtype in (DLG_TYPE_PULLDOWN, + DLG_TYPE_YESNOSUSPEND, + DLG_TYPE_YESNO) and not self.options.agentpid: + sys.stderr.write("Agent pid not supplied via --parent\n") + sys.exit(EXIT_FAILURE) - if dlgtype == DLG_TYPE_PULLDOWN and not self.options.window: - sys.stderr.write("Window id not supplied via --window\n") - sys.exit(EXIT_FAILURE) + if dlgtype == DLG_TYPE_PULLDOWN and not self.options.window: + sys.stderr.write("Window id not supplied via --window\n") + sys.exit(EXIT_FAILURE) - if self.options.caption: - message_caption = self.options.caption - else: - message_caption = sys.argv[0] + if self.options.caption: + message_caption = self.options.caption + else: + message_caption = sys.argv[0] - if self.options.text: - message_text = self.options.text - else: - message_text = "" + if self.options.text: + message_text = self.options.text + else: + message_text = "" - if self.options.display: - os.environ["DISPLAY"] = self.options.display + if self.options.display: + os.environ["DISPLAY"] = self.options.display - if dlgtype == DLG_TYPE_OK: - ShowSimpleMessageBox(gtk.MESSAGE_INFO, message_caption, message_text) + if dlgtype == DLG_TYPE_OK: + show_simple_message_box( + gtk.MESSAGE_INFO, message_caption, message_text) - elif dlgtype in (DLG_TYPE_ERROR, DLG_TYPE_PANIC): - ShowSimpleMessageBox(gtk.MESSAGE_ERROR, message_caption, message_text) + elif dlgtype in (DLG_TYPE_ERROR, DLG_TYPE_PANIC): + show_simple_message_box( + gtk.MESSAGE_ERROR, message_caption, message_text) - elif dlgtype == DLG_TYPE_PULLDOWN: - HandleSessionAction(self.options.agentpid, - PullDownMenu(self.options.window).Show()) + elif dlgtype == DLG_TYPE_PULLDOWN: + handle_session_action(self.options.agentpid, + PullDownMenu(self.options.window).show()) - elif dlgtype == DLG_TYPE_YESNOSUSPEND: - HandleSessionAction(self.options.agentpid, - ShowYesNoSuspendBox(message_caption, message_text)) + elif dlgtype == DLG_TYPE_YESNOSUSPEND: + handle_session_action(self.options.agentpid, + show_yes_no_suspend_box(message_caption, message_text)) - elif dlgtype == DLG_TYPE_YESNO: - HandleSessionAction(self.options.agentpid, - ShowYesNoBox(message_caption, message_text)) + elif dlgtype == DLG_TYPE_YESNO: + handle_session_action(self.options.agentpid, + show_yes_no_box(message_caption, message_text)) - else: - # TODO: Implement all dialog types - sys.stderr.write("Dialog type '%s' not implemented" % (dlgtype)) - sys.exit(EXIT_FAILURE) + else: + # TODO: Implement all dialog types + sys.stderr.write("Dialog type '%s' not implemented" % (dlgtype)) + sys.exit(EXIT_FAILURE) -NxDialogProgram().Main() +NxDialogProgram().main() -- cgit v1.2.3 From 3a097e6e08ecafc280c1c13dd4329cb5fbcd3284 Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Thu, 28 Feb 2019 17:49:28 +0100 Subject: nxdialog: use gtk3 instead of gtk2 --- nxdialog/nxdialog | 63 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog index f7c061e17..5414691c4 100755 --- a/nxdialog/nxdialog +++ b/nxdialog/nxdialog @@ -30,6 +30,7 @@ # - added missing docstrings # - pylint improvements # - removed neatx entry from the pulldoww menu +# - use PyGObject instead of PyGtk and thus Gtk3 """nxdialog program for handling dialog display.""" @@ -41,10 +42,10 @@ import os import signal import sys -import gtk - -import pygtk -pygtk.require("2.0") +import gi +gi.require_version('Gtk', '3.0') +# pylint: disable=wrong-import-position +from gi.repository import Gtk, Gdk, GdkX11 PROGRAM = "nxdialog" @@ -95,58 +96,62 @@ class PullDownMenu(object): def show(self): """ Shows popup and returns result. """ - win = gtk.gdk.window_foreign_new(self.window_id) + display = Gdk.Display.get_default() + win = GdkX11.X11Window.foreign_new_for_display(display, self.window_id) - menu = gtk.Menu() + menu = Gtk.Menu() menu.connect("deactivate", self.menu_deactivate) # TODO: Show title item in bold font - title = gtk.MenuItem(label="Session control") + title = Gtk.MenuItem(label="Session control") title.set_sensitive(False) menu.append(title) - disconnect = gtk.MenuItem(label=DISCONNECT_TEXT) + disconnect = Gtk.MenuItem(label=DISCONNECT_TEXT) disconnect.connect("activate", self.item_activate, DISCONNECT) menu.append(disconnect) - terminate = gtk.MenuItem(label=TERMINATE_TEXT) + terminate = Gtk.MenuItem(label=TERMINATE_TEXT) terminate.connect("activate", self.item_activate, TERMINATE) menu.append(terminate) - menu.append(gtk.SeparatorMenuItem()) + menu.append(Gtk.SeparatorMenuItem()) - cancel = gtk.MenuItem(label=CANCEL_TEXT) + cancel = Gtk.MenuItem(label=CANCEL_TEXT) menu.append(cancel) menu.show_all() menu.popup(parent_menu_shell=None, parent_menu_item=None, func=self.pos_menu, data=win, - button=0, activate_time=gtk.get_current_event_time()) + button=0, activate_time=Gtk.get_current_event_time()) - gtk.main() + Gtk.main() return self.result def item_activate(self, _, result): """ called when a menu item is selected """ self.result = result - gtk.main_quit() + Gtk.main_quit() @staticmethod def menu_deactivate(_): """ called when menu is deactivated """ - gtk.main_quit() + Gtk.main_quit() @staticmethod - def pos_menu(menu, parent): + def pos_menu(menu, _xpos, _ypos, *data): """ Positions menu at the top center of the parent window. """ + parent = data[0] + # Get parent geometry and origin - (_, _, win_width, _, _) = parent.get_geometry() - (win_x, win_y) = parent.get_origin() + _, _, win_width, _ = parent.get_geometry() + _, win_x, win_y = parent.get_origin() # Calculate width of menu - (menu_width, _) = menu.size_request() + #menu_width = menu.get_preferred_width().natural_width + menu_width = menu.get_allocated_width() # Calculate center center_x = win_x + ((win_width - menu_width) / 2) @@ -164,12 +169,13 @@ def show_yes_no_suspend_box(title, text): @return: Choosen action """ - dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) + dlg = Gtk.MessageDialog(type=Gtk.MessageType.QUESTION, + flags=Gtk.DialogFlags.MODAL) dlg.set_title(title) dlg.set_markup(text) dlg.add_button(DISCONNECT_TEXT, DISCONNECT) dlg.add_button(TERMINATE_TEXT, TERMINATE) - dlg.add_button(CANCEL_TEXT, gtk.RESPONSE_CANCEL) + dlg.add_button(CANCEL_TEXT, Gtk.ResponseType.CANCEL) res = dlg.run() @@ -190,11 +196,12 @@ def show_yes_no_box(title, text): @return: Choosen action """ - dlg = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION, flags=gtk.DIALOG_MODAL) + dlg = Gtk.MessageDialog(type=Gtk.MessageType.QUESTION, + flags=Gtk.DialogFlags.MODAL) dlg.set_title(title) dlg.set_markup(text) dlg.add_button(YES_TEXT, TERMINATE) - dlg.add_button(NO_TEXT, gtk.RESPONSE_CANCEL) + dlg.add_button(NO_TEXT, Gtk.ResponseType.CANCEL) res = dlg.run() @@ -241,8 +248,8 @@ def show_simple_message_box(icon, title, text): @param text: Message box text """ - dlg = gtk.MessageDialog(type=icon, flags=gtk.DIALOG_MODAL, - buttons=gtk.BUTTONS_OK) + dlg = Gtk.MessageDialog(type=icon, flags=Gtk.DialogFlags.MODAL, + buttons=Gtk.ButtonsType.OK) dlg.set_title(title) dlg.set_markup(text) dlg.run() @@ -304,7 +311,7 @@ class NxDialogProgram(object): default: info) [currently unimplemented]"), optparse.make_option("--local", action="store_true", dest="local", help="specify that proxy mode is used \ - [currently unimplemented]"), + [currently unimplemented]"), optparse.make_option("--allowmultiple", action="store_true", dest="allowmultiple", help="allow launching more than one dialog with \ @@ -349,11 +356,11 @@ class NxDialogProgram(object): if dlgtype == DLG_TYPE_OK: show_simple_message_box( - gtk.MESSAGE_INFO, message_caption, message_text) + Gtk.MessageType.INFO, message_caption, message_text) elif dlgtype in (DLG_TYPE_ERROR, DLG_TYPE_PANIC): show_simple_message_box( - gtk.MESSAGE_ERROR, message_caption, message_text) + Gtk.MessageType.ERROR, message_caption, message_text) elif dlgtype == DLG_TYPE_PULLDOWN: handle_session_action(self.options.agentpid, -- cgit v1.2.3 From 57700cd6b2c6285c27a78758b741247faf9cf6b0 Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Thu, 28 Feb 2019 18:13:47 +0100 Subject: nxdialog: convert from optparse to argparse --- nxdialog/nxdialog | 76 ++++++++++++++++++++++++++----------------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog index 5414691c4..ff453e22f 100755 --- a/nxdialog/nxdialog +++ b/nxdialog/nxdialog @@ -31,13 +31,14 @@ # - pylint improvements # - removed neatx entry from the pulldoww menu # - use PyGObject instead of PyGtk and thus Gtk3 +# - replace optparse by argparse """nxdialog program for handling dialog display.""" # If an "NX_CLIENT" environment variable is not provided to nxagent # nxcomp library assumes this script is located in /usr/NX/bin/nxclient -import optparse +import argparse import os import signal import sys @@ -265,7 +266,7 @@ class NxDialogProgram(object): def main(self): """ let's do something """ try: - (self.options, self.args) = self.parse_args() + self.options = self.parse_args() self.run() @@ -278,45 +279,40 @@ class NxDialogProgram(object): def parse_args(self): """ init parser """ - parser = optparse.OptionParser(option_list=self.build_options(), - formatter=optparse.TitledHelpFormatter()) - return parser.parse_args() - @staticmethod - def build_options(): - """ build options for the parser """ - return [ - # nxagent 3.5.99.18 only uses yesno, ok, pulldown and yesnosuspend - # yesno dialogs will always kill the session if "yes" is selected - optparse.make_option("--dialog", type="string", dest="dialog_type", - help='type of dialog to show, one of "yesno", \ - "ok", "error", "panic", "quit", "pulldown", \ - "yesnosuspend"'), - optparse.make_option("--message", type="string", dest="text", - help="message text to display in the dialog"), - optparse.make_option("--caption", type="string", dest="caption", - help="window title of the dialog"), - optparse.make_option("--display", type="string", dest="display", - help="X11 display where the dialog should be \ - shown"), - optparse.make_option("--parent", type="int", dest="agentpid", - help="pid of the nxagent"), - optparse.make_option("--window", type="int", dest="window", - help="id of window where to embed the \ - pulldown dialog type"), - # -class, -local, -allowmultiple are unused in nxlibs 3.5.99.18 - optparse.make_option("--class", type="string", dest="dlgclass", - default="info", - help="class of the message (info, warning, error) \ - default: info) [currently unimplemented]"), - optparse.make_option("--local", action="store_true", dest="local", - help="specify that proxy mode is used \ - [currently unimplemented]"), - optparse.make_option("--allowmultiple", action="store_true", - dest="allowmultiple", - help="allow launching more than one dialog with \ - the same message [currently unimplemented]"), - ] + parser = argparse.ArgumentParser(description="Helper for nxagent to display dialogs") + + # nxagent 3.5.99.18 only uses yesno, ok, pulldown and yesnosuspend + # yesno dialogs will always kill the session if "yes" is selected + parser.add_argument("--dialog", dest="dialog_type", + help='type of dialog to show, one of "yesno", \ + "ok", "error", "panic", "quit", "pulldown", \ + "yesnosuspend"') + parser.add_argument("--message", dest="text", + help="message text to display in the dialog") + parser.add_argument("--caption", dest="caption", + help="window title of the dialog") + parser.add_argument("--display", dest="display", + help="X11 display where the dialog should be \ + shown") + parser.add_argument("--parent", type=int, dest="agentpid", + help="pid of the nxagent") + parser.add_argument("--window", type=int, dest="window", + help="id of window where to embed the \ + pulldown dialog type") + # -class, -local, -allowmultiple are unused in nxlibs 3.5.99.18 + parser.add_argument("--class", dest="dlgclass", default="info", + help="class of the message (info, warning, error) \ + default: info) [currently unimplemented]") + parser.add_argument("--local", action="store_true", dest="local", + help="specify that proxy mode is used \ + [currently unimplemented]") + parser.add_argument("--allowmultiple", action="store_true", + dest="allowmultiple", + help="allow launching more than one dialog with \ + the same message [currently unimplemented]") + + return parser.parse_args() def run(self): """ Disconnect/terminate NX session upon user's request. """ -- cgit v1.2.3 From acffe15b58c352914a63499d29a12f72671afc5b Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Thu, 28 Feb 2019 20:17:39 +0100 Subject: nxdialog: make code compatible to python2 _and_ python3 --- nxdialog/nxdialog | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog index ff453e22f..58e773ca8 100755 --- a/nxdialog/nxdialog +++ b/nxdialog/nxdialog @@ -1,5 +1,11 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python # +# ^^^ This is working with python2 and python3 so we choose a shebang +# that will find either version. +# Citing PEP394: "One exception to this is scripts that are +# deliberately written to be source compatible with both Python +# 2.x and 3.x. Such scripts may continue to use python on their +# shebang line. # Copyright (C) 2008 Google Inc. # Copyright (C) 2019 Ulrich Sibiller @@ -32,12 +38,15 @@ # - removed neatx entry from the pulldoww menu # - use PyGObject instead of PyGtk and thus Gtk3 # - replace optparse by argparse +# - make code compatible to python2 and python3. """nxdialog program for handling dialog display.""" # If an "NX_CLIENT" environment variable is not provided to nxagent # nxcomp library assumes this script is located in /usr/NX/bin/nxclient +from __future__ import print_function + import argparse import os import signal @@ -155,7 +164,7 @@ class PullDownMenu(object): menu_width = menu.get_allocated_width() # Calculate center - center_x = win_x + ((win_width - menu_width) / 2) + center_x = int(win_x + ((win_width - menu_width) / 2)) return (center_x, win_y, True) @@ -224,11 +233,11 @@ def handle_session_action(agentpid, action): """ if action == DISCONNECT: - print "Disconnecting from session, sending SIGHUP to %s" % (agentpid) + print("Disconnecting from session, sending SIGHUP to %s" % (agentpid)) os.kill(agentpid, signal.SIGHUP) elif action == TERMINATE: - print "Terminating session, sending SIGTERM to process %s" % (agentpid) + print("Terminating session, sending SIGTERM to process %s" % (agentpid)) os.kill(agentpid, signal.SIGTERM) elif action is None: @@ -273,11 +282,12 @@ class NxDialogProgram(object): except (SystemExit, KeyboardInterrupt): raise - except Exception, expt: + except Exception as expt: sys.stderr.write("Caught exception: %s\n" % (expt)) sys.exit(EXIT_FAILURE) - def parse_args(self): + @staticmethod + def parse_args(): """ init parser """ parser = argparse.ArgumentParser(description="Helper for nxagent to display dialogs") -- cgit v1.2.3 From 07052a25c0295806b50cf52191f98986b40e8b38 Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Fri, 1 Mar 2019 10:50:16 +0100 Subject: nxdialog: allow to provide --parent 0 for testing Agent PID 0 can be used for testing --- nxdialog/nxdialog | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog index 58e773ca8..d3a7c0bb0 100755 --- a/nxdialog/nxdialog +++ b/nxdialog/nxdialog @@ -234,11 +234,13 @@ def handle_session_action(agentpid, action): if action == DISCONNECT: print("Disconnecting from session, sending SIGHUP to %s" % (agentpid)) - os.kill(agentpid, signal.SIGHUP) + if agentpid != 0: + os.kill(agentpid, signal.SIGHUP) elif action == TERMINATE: print("Terminating session, sending SIGTERM to process %s" % (agentpid)) - os.kill(agentpid, signal.SIGTERM) + if agentpid != 0: + os.kill(agentpid, signal.SIGTERM) elif action is None: pass @@ -339,7 +341,7 @@ class NxDialogProgram(object): if dlgtype in (DLG_TYPE_PULLDOWN, DLG_TYPE_YESNOSUSPEND, - DLG_TYPE_YESNO) and not self.options.agentpid: + DLG_TYPE_YESNO) and self.options.agentpid is None: sys.stderr.write("Agent pid not supplied via --parent\n") sys.exit(EXIT_FAILURE) -- cgit v1.2.3 From d0e40f8b2aea32643708892a26a72349ecc82299 Mon Sep 17 00:00:00 2001 From: Ulrich Sibiller Date: Fri, 1 Mar 2019 10:52:39 +0100 Subject: nxdialog: add example call --- nxdialog/nxdialog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nxdialog/nxdialog b/nxdialog/nxdialog index d3a7c0bb0..e7ca10aad 100755 --- a/nxdialog/nxdialog +++ b/nxdialog/nxdialog @@ -44,6 +44,9 @@ # If an "NX_CLIENT" environment variable is not provided to nxagent # nxcomp library assumes this script is located in /usr/NX/bin/nxclient +# +# Example: +# nxdialog --dialog yesno --message "message text" --caption "message title" --parent 0 from __future__ import print_function -- cgit v1.2.3