#!/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()