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 (limited to '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