Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NUT-Monitor: report NUT version and website for this build in About dialog #2845

Merged
merged 19 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
408ba6f
tools/nut-usbinfo.pl: find_usbdevs(): skip subdirs for now
jimklimov Mar 9, 2025
a68345c
scripts/python/app/NUT-Monitor-py3qt5.in: whitespace fixes
jimklimov Mar 12, 2025
40a8a91
scripts/python/app/{ui,locale}/*: revise About dialog and license inf…
jimklimov Mar 9, 2025
defdac4
scripts/python/app/NUT-Monitor-{py2gtk2,py3qt5}.in: gui_about_dialog(…
jimklimov Mar 12, 2025
3180b20
scripts/python/app/{ui,locale}/*: adjust localization of About dialog…
jimklimov Mar 12, 2025
4b68ae9
scripts/python/app/NUT-Monitor-py3qt5.in: troubleshoot the localizati…
jimklimov Mar 12, 2025
8426fd1
scripts/python/app/NUT-Monitor-py3qt5.in: change to script dir, to fi…
jimklimov Mar 13, 2025
76704bc
scripts/python/app/NUT-Monitor-py2gtk2.in: change to script dir, to f…
jimklimov Mar 13, 2025
1834f5b
scripts/python/app/NUT-Monitor-py2gtk2.in: fix list copy syntax for P…
jimklimov Mar 13, 2025
ea3c542
scripts/python/app/NUT-Monitor-py2gtk2.in: fix rewriting of About dia…
jimklimov Mar 13, 2025
ca30653
scripts/python/app/README.adoc, docs/nut.dict: note environments to t…
jimklimov Mar 13, 2025
65c0d66
scripts/python/app/NUT-Monitor-py2gtk2.in: TRY TO fix UTF-8 resource …
jimklimov Mar 13, 2025
36d07f2
scripts/python/app/*, NEWS.adoc: bump NUT-Monitor-py* releases to 2.0.2
jimklimov Mar 12, 2025
b1915cf
scripts/python/app/{ui,locale}/*: maintain plaintext (py2gtk2) and HT…
jimklimov Mar 13, 2025
c7ba375
scripts/python/app/*, NEWS.adoc: NUT-Monitor-py3qt5 localization issu…
jimklimov Mar 12, 2025
e4e5ef3
Revert "scripts/python/app/*, NEWS.adoc: NUT-Monitor-py3qt5 localizat…
jimklimov Mar 12, 2025
aae2872
scripts/python/app/README.adoc: clarify what was wrong with localizat…
jimklimov Mar 12, 2025
4190128
scripts/python/app/README.adoc: note localization of standard UI elem…
jimklimov Mar 13, 2025
fbb28a5
scripts/python/app/locale/{fr,it,ru}/LC_MESSAGES/NUT-Monitor.mo: rege…
jimklimov Mar 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,11 @@ relocated into new `shutdown.default` INSTCMD definitions. [#2670]

- added man page for the `NUT-Monitor` Python UI client.

- The `NUT-Monitor` Python UI client itself was revised to report the
`PACKAGE_VERSION` and `NUT_WEBSITE_BASE` strings in the "About" dialog
contents; localization support for the dialog and some other resources
was revised to work in Py3Qt5 variant of the script. [#722]

- enabled installation of built single-file PDF and HTML (including man page
renditions) under the configured `docdir`. It seems previously they were
only built (if requested) but not installed via `make`, unlike the common
Expand Down
5 changes: 4 additions & 1 deletion docs/nut.dict
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
personal_ws-1.1 en 3329 utf-8
personal_ws-1.1 en 3332 utf-8
AAC
AAS
ABI
Expand Down Expand Up @@ -902,6 +902,7 @@ PULS
PV
PWR
PXG
PYTHONIOENCODING
PYTHONPATH
PaaS
Pac
Expand Down Expand Up @@ -2720,7 +2721,9 @@ py
pycparser
pydoc
pygments
pygtk
pynut
pyqt
qDEB
qa
qemu
Expand Down
66 changes: 56 additions & 10 deletions scripts/python/app/NUT-Monitor-py2gtk2.in
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#
# 2015-02-14 Michal Fincham - Version 1.3.1
# Corrected unsafe permissions on ~/.nut-monitor (Debian #777706)
#
# 2025-03-15 Jim Klimov - Version 2.0.2 (to align with Python3 variant)
# Revise localisation, inject PACKAGE_VERSION and NUT_WEBSITE_BASE
# into the About dialog contents


import gtk, gtk.glade, gobject
Expand All @@ -41,9 +45,12 @@ import ConfigParser
import locale
import gettext

if platform.system() == "Windows":
import xml.dom.minidom

# Try local development/accompanying packaging first,
# then system installation
sys_path_orig = sys.path.copy()
sys_path_orig = list(sys.path)
try:
# Try "module" path relative to this program script:
mod_path = os.path.sep.join([
Expand All @@ -66,6 +73,10 @@ except Exception as ignored:
import PyNUT


# We would seek locale files relative to script dir
os.chdir(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))
# print(os.getcwd())

# Activate threadings on glib
gobject.threads_init()

Expand Down Expand Up @@ -102,9 +113,26 @@ class interface :
# the `nut-monitor-py2gtk2.desktop` for windowing resources:
# g_set_prgname('nut-monitor-py2gtk2')

self.__glade_file = os.path.join( os.path.dirname( sys.argv[0] ), "ui/gui-1.3.glade" )
self.__glade_file = os.path.join( "ui", "gui-1.3.glade" )
if os.path.exists(self.__glade_file):
self.__resdir = "."
else:
self.__resdir = os.path.dirname( sys.argv[0] )
self.__glade_file = os.path.join(self.__resdir, "ui", "gui-1.3.glade" )
# If not exists, we bail out in load attempt below

if ( platform.system() == "Windows" ) :
### Fails for "fr" and "ru" locales with errors decoding UTF-8 in
### both XML() and xml_new_from_buffer() despite any trickery like
### attempts done below. PRs welcome:
# with open(self.__glade_file, 'rb') as f:
# doc = f.read() # .decode("cp1250").encode("utf8")
# self.__widgets["interface"] = gtk.glade.xml_new_from_buffer( doc, len(doc), "window1", APP )
doc = xml.dom.minidom.parse(self.__glade_file).toxml().encode("utf-8")
self.__widgets["interface"] = gtk.glade.xml_new_from_buffer( doc, len(doc), "window1", APP )
else :
self.__widgets["interface"] = gtk.glade.XML( self.__glade_file, "window1", APP )

self.__widgets["interface"] = gtk.glade.XML( self.__glade_file, "window1", APP )
self.__widgets["main_window"] = self.__widgets["interface"].get_widget("window1")
self.__widgets["status_bar"] = self.__widgets["interface"].get_widget("statusbar2")
self.__widgets["ups_host_entry"] = self.__widgets["interface"].get_widget("entry1")
Expand Down Expand Up @@ -135,11 +163,11 @@ class interface :

# Create the tray icon and connect it to the show/hide method...
self.__widgets["status_icon"] = gtk.StatusIcon()
self.__widgets["status_icon"].set_from_file( os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", "on_line.png" ) )
self.__widgets["status_icon"].set_from_file( os.path.join( self.__resdir, "pixmaps", "on_line.png" ) )
self.__widgets["status_icon"].set_visible( True )
self.__widgets["status_icon"].connect( "activate", self.tray_activated )

self.__widgets["ups_status_image"].set_from_file( os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", "on_line.png" ) )
self.__widgets["ups_status_image"].set_from_file( os.path.join( self.__resdir, "pixmaps", "on_line.png" ) )

# Define interface callbacks actions
self.__callbacks = { "on_window1_destroy" : self.quit,
Expand Down Expand Up @@ -297,8 +325,8 @@ class interface :
#-------------------------------------------------------------------
# Change the status icon and tray icon
def change_status_icon( self, icon="on_line", blink=False ) :
self.__widgets["status_icon"].set_from_file( os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", "%s.png" % icon ) )
self.__widgets["ups_status_image"].set_from_file( os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", "%s.png" % icon ) )
self.__widgets["status_icon"].set_from_file( os.path.join( self.__resdir, "pixmaps", "%s.png" % icon ) )
self.__widgets["ups_status_image"].set_from_file( os.path.join( self.__resdir, "pixmaps", "%s.png" % icon ) )
self.__widgets["status_icon"].set_blinking( blink )

#-------------------------------------------------------------------
Expand Down Expand Up @@ -631,7 +659,25 @@ class interface :
# Display the about dialog
def gui_about_dialog( self, widget=None ) :
dialog_interface = gtk.glade.XML( self.__glade_file, "aboutdialog1" )
### sys.stderr.write("gui_about_dialog(): dialog_interface=%s\n" % str(dir(dialog_interface)))
### sys.stderr.write("gui_about_dialog(): dialog_interface.props=%s\n" % str(dir(dialog_interface.props)))
### sys.stderr.write("gui_about_dialog(): dialog_interface.get_data=%s\n" % str(dir(dialog_interface.get_data("label"))))
### sys.stderr.write("gui_about_dialog(): dialog_interface.get_properties=%s\n" % str(dir(dialog_interface.get_properties("label"))))
dialog = dialog_interface.get_widget( "aboutdialog1" )
### sys.stderr.write("gui_about_dialog(): dialog=%s\n" % str(dir(dialog)))
### sys.stderr.write("gui_about_dialog(): label=%s\n" % str(dir(dialog.label)))
### sys.stderr.write("gui_about_dialog(): text=%s\n" % str(dir(dialog.label.text)))
### sys.stderr.write("gui_about_dialog(): text()=%s\n" % str(dir(dialog.label.text())))
s = dialog.get_comments()
### sys.stderr.write("gui_about_dialog(): s (original) ='%s'\n" % s)
s = _(s)
### sys.stderr.write("gui_about_dialog(): s (localized) ='%s'\n" % s)
s = (s
.replace('%s%s%s' % ("@", "PACKAGE_VERSION", "@"), '@PACKAGE_VERSION@')
.replace('%s%s%s' % ("@", "NUT_WEBSITE_BASE", "@"), '@NUT_WEBSITE_BASE@')
)
### sys.stderr.write("gui_about_dialog(): s (rewritten) ='%s'\n" % s)
dialog.set_comments(s)

self.__widgets["main_window"].set_sensitive( False )
dialog.run()
Expand Down Expand Up @@ -662,7 +708,7 @@ class interface :
pynotify.init( "NUT Monitor" )

if ( icon_file != "" ) :
icon = "file://%s" % os.path.abspath( os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", icon_file ) )
icon = "file://%s" % os.path.abspath( os.path.join( self.__resdir, "pixmaps", icon_file ) )
else :
icon = None

Expand Down Expand Up @@ -759,9 +805,9 @@ class interface :

for k,v in vars.iteritems() :
if ( rwvars.has_key( k ) ) :
icon_file = os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", "var-rw.png" )
icon_file = os.path.join( self.__resdir, "pixmaps", "var-rw.png" )
else :
icon_file = os.path.join( os.path.dirname( sys.argv[0] ), "pixmaps", "var-ro.png" )
icon_file = os.path.join( self.__resdir, "pixmaps", "var-ro.png" )

icon = gtk.gdk.pixbuf_new_from_file( icon_file )
self.__widgets["ups_vars_tree_store"].append( [ icon, k, v ] )
Expand Down
33 changes: 31 additions & 2 deletions scripts/python/app/NUT-Monitor-py3qt5.in
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
#
# 2023-11-27 Laurent Bigonville - Version 2.0.1
# Set the DesktopFileName
#
# 2025-03-15 Jim Klimov - Version 2.0.2
# Revise localisation, inject PACKAGE_VERSION and NUT_WEBSITE_BASE
# into the About dialog contents


import PyQt5.uic
Expand Down Expand Up @@ -74,6 +78,9 @@ except Exception as ignored:
### sys.stderr.write("[D2] sys.path: %s\n" % sys.path)
import PyNUT

# We would seek locale files relative to script dir
os.chdir(os.path.dirname(os.path.abspath(os.path.realpath(__file__))))
# print(os.getcwd())

class interface :

Expand Down Expand Up @@ -234,7 +241,11 @@ class interface :
self.__app.exec()

def __find_res_file( self, ftype, filename ) :
# print("%s ~ %s => '%s' '%s'\n" % (os.getcwd(), os.path.dirname( sys.argv[0] ), ftype, filename))
filename = os.path.join( ftype, filename )
# We would be in script dir
if os.path.exists(filename):
return filename
# TODO: Skip checking application directory if installed
path = os.path.join( os.path.dirname( sys.argv[0] ), filename )
if os.path.exists(path):
Expand All @@ -248,6 +259,10 @@ class interface :
filename = 'nut-monitor.png'

# TODO: Skip checking application directory if installed
path = os.path.join( "icons", "256x256", filename )
if os.path.exists(path):
return path

path = os.path.join( os.path.dirname( sys.argv[0] ), "icons", "256x256", filename )
if os.path.exists(path):
return path
Expand Down Expand Up @@ -641,6 +656,19 @@ class interface :

dialog = PyQt5.uic.loadUi( dialog_ui_file )
dialog.icon.setPixmap( QPixmap( self.__find_icon_file() ) )
### sys.stderr.write("gui_about_dialog(): dialog=%s\n" % str(dir(dialog)))
### sys.stderr.write("gui_about_dialog(): label=%s\n" % str(dir(dialog.label)))
### sys.stderr.write("gui_about_dialog(): text=%s\n" % str(dir(dialog.label.text)))
s = dialog.label.text()
### sys.stderr.write("gui_about_dialog(): s (original) ='%s'\n" % s)
s = _(s)
### sys.stderr.write("gui_about_dialog(): s (localized) ='%s'\n" % s)
s = (s
.replace('%s%s%s' % ("@", "PACKAGE_VERSION", "@"), '@PACKAGE_VERSION@')
.replace('%s%s%s' % ("@", "NUT_WEBSITE_BASE", "@"), '@NUT_WEBSITE_BASE@')
)
### sys.stderr.write("gui_about_dialog(): s (rewritten) ='%s'\n" % s)
dialog.label.setText(s)

credits_button = QPushButton( dialog )
credits_button.setText( _("C&redits") )
Expand All @@ -650,7 +678,7 @@ class interface :
licence_button = QPushButton( dialog )
licence_button.setText( _("&Licence") )
licence_button.clicked.connect( self.gui_about_licence )

dialog.buttonBox.addButton( credits_button, QDialogButtonBox.HelpRole )
dialog.buttonBox.addButton( licence_button, QDialogButtonBox.HelpRole )

Expand All @@ -671,6 +699,7 @@ Daniele Pezzini <hyouko@gmail.com> - Italiano
def gui_about_licence( self ) :
QMessageBox.about( None, _("Licence"), _("""
Copyright (C) 2010 David Goncalves <david@lestat.st>
Copyright (C) since 2010 by NUT Community

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
Expand Down Expand Up @@ -815,7 +844,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.

# Try to resize the main window...
self.__widgets["main_window"].adjustSize()

#-------------------------------------------------------------------
# Disconnect from the UPS
def disconnect_from_ups( self, widget=None ) :
Expand Down
57 changes: 55 additions & 2 deletions scripts/python/app/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,20 @@ or:
Localization
------------

For localized UI, also `export LANG=fr_FR.UTF-8` or `export LANG=ru_RU.UTF-8`
For localized UI, you can also request the locale with different precision like
`export LANG=fr_FR.UTF-8`, `export LANG=it_IT` or `export LANG=ru`
(see and feel welcome to improve the choice of languages in `locale` directory).

NOTE: Currently localization only works for Python 2 client, PRs are welcome.
NOTE: Currently, the localization only fully works for Python 2 client, while
menu names, dialog field labels, etc. remain in default English wording despite
the presence of translations in resource files. PRs to attach them to run-time
rendering in the Python 3 client are welcome.

Note that some items, like the standard menu items for Preferences or About,
can come from the interpreter's default library and so partially cover the
interface elements in other languages (e.g. system default) independently of
the resources provided with the script or its sources (at least in the
Python 2 / GTK2 variant of the UI).

Desktop menu integration
------------------------
Expand All @@ -55,6 +65,49 @@ image::screenshots/nut-monitor-2.png[Example report of device variables]

image::screenshots/nut-monitor-3.png[Example modification of a writable variable]

Development/Testing notes
-------------------------

As Python2 falls out of favor and packaging scope of different distributions,
one where both variants of the script can be tested simultaneously as of 2025
is MSYS2 (on Windows), see `docs/config-prereqs.txt` in NUT sources for more
details about general prerequisites for the NUT build in that environment.

Regarding the Python UI support, you would need:

----
:; pacman -Sy mingw-w64-x86_64-python2-pygtk mingw-w64-x86_64-python-pyqt5
----

This should pull in the interpreters and libraries involved.

You may have to add this to your `~/.bashrc` for Python 2 to work properly:

----
PYTHONIOENCODING=UTF-8
export PYTHONIOENCODING
----

To iterate during specifically localization development, you can edit the `.in`
template sources of the scripts and the localization files, and (from the `app`
directory) run with:

----
:; (cd .. && make `pwd`/app/locale/*/*/*) ; (cd ../../../ && ./config.status \
scripts/python/app/NUT-Monitor-py2gtk2 scripts/python/app/NUT-Monitor-py3qt5) \
&& LANG=fr_FR ./NUT-Monitor-py2gtk2
----

or `..../NUT-Monitor-py3qt5` respectively.

NOTE: While `LANG=it` can be useful on MSYS2 for testing that localization does
take place, it proved problematic to load `fr` and `ru` resources -- logic
buried inside `gtk.glade.XML()` and `gtk.glade.xml_new_from_buffer()` gets
the UTF-8 encoded data from `ui/gui-1.3.glade`, but then apparently mangles
it inside (possibly in the DLL used by the Python module) to some other
encoding, and then fails to parse that as UTF-8 (broken non-ASCII characters
in `fr`, crash in `ru`). PRs with fixes would be welcome!

Kudos
-----

Expand Down
43 changes: 33 additions & 10 deletions scripts/python/app/locale/NUT-Monitor.pot
Original file line number Diff line number Diff line change
Expand Up @@ -348,26 +348,49 @@ msgid "Enter a new value for the variable.\n"
msgstr ""

#: gui-1.3.glade.h:29
msgid "Copyright (c) 2010 David Goncalves"
msgid "Copyright (c) 2010 David Goncalves, Copyright (c) after 2010 NUT Community"
msgstr ""

#: gui-1.3.glade.h:30
msgid ""
"GUI to manage devices connected a NUT server.\n"
# NOTE: This plaintext markup of the "comment" is used
# in the Python2/GTK2 version of NUT Monitor.
# Equivalent "label" text in HTML markup is used in
# Python3/Qt5 localization.

#: gui-1.3.glade.h:35
msgid "GUI to manage devices connected to a NUT server.\n"
"Provided with NUT version @PACKAGE_VERSION@.\n"
"\n"
"For more information about NUT (Network UPS Tools)\n"
"please visit the author's website.\n"
"For more information about the GUI please visit\n"
"the author's web-site:\n"
"https://www.lestat.st/en/informatique/projets/nut-monitor\n"
"\n"
"https://www.networkupstools.org\n"
"For more information about NUT (Network UPS Tools)\n"
"please visit the project web-site:\n"
"@NUT_WEBSITE_BASE@\n"
msgstr ""

#: gui-1.3.glade.h:46
msgid "<h1>NUT-Monitor 2.0.2</h1>\n"
"<p>GUI to manage devices connected to a NUT server.<br/>\n"
"Provided with NUT version @PACKAGE_VERSION@.</p>\n"
"<p style=\" font-size:8pt;\">Copyright (C) 2010 David Goncalves<br/>\n"
"Copyright (C) since 2010 by NUT Community</p>\n"
"<p>For more information about NUT (Network UPS Tools)<br/>\n"
"please visit the project web-site:</p>\n"
"<p style=\"margin-bottom: 1.5em\"><a href=\"@NUT_WEBSITE_BASE@\">@NUT_WEBSITE_BASE@</a></p>\n"
"<p>For more information about the GUI please visit<br/>\n"
"the author's web-site:</p>\n"
"<p><a href=\"https://www.lestat.st/en/informatique/projets/nut-monitor\">https://www.lestat.st</a></p>"
msgstr ""

#: gui-1.3.glade.h:37
msgid "http://www.lestat.st"
#: gui-1.3.glade.h:58
msgid "https://www.lestat.st"
msgstr ""

#: gui-1.3.glade.h:38
#: gui-1.3.glade.h:59
msgid ""
"Copyright (C) 2010 David Goncalves <david@lestat.st>\n"
"Copyright (C) since 2010 by NUT Community\n"
"\n"
"This program is free software: you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
Expand Down
Binary file modified scripts/python/app/locale/fr/LC_MESSAGES/NUT-Monitor.mo
Binary file not shown.
Loading