Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package vdu_controls for openSUSE:Factory 
checked in at 2026-04-07 16:33:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/vdu_controls (Old)
 and      /work/SRC/openSUSE:Factory/.vdu_controls.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "vdu_controls"

Tue Apr  7 16:33:45 2026 rev:18 rq:1344800 version:2.5.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/vdu_controls/vdu_controls.changes        
2025-08-29 18:38:23.930241821 +0200
+++ /work/SRC/openSUSE:Factory/.vdu_controls.new.21863/vdu_controls.changes     
2026-04-07 16:49:26.993882350 +0200
@@ -1,0 +2,22 @@
+Mon Apr  6 20:08:10 UTC 2026 - Michael Hamilton <[email protected]>
+
+- Version 2.5.0
+  * Visual refresh of the Main-panel. 
+  * Added option "toolbar-at-top" to configure the top/bottom placement of the 
toolbar 
+    in the main-window. Top placement is more Plasma-6-like and may also be 
useful
+    when combined with top-located system-trays 
+  * Added option "separate-status-bar" to allow the main-window's status-bar 
to be 
+    separated from its toolbar.  This may be useful when combined with 
"toolbar-at-top".
+  * Replaced QProgressBar with a more modern circular busy-spinner.
+  * Added a tooltip to the status-bar that shows the last 10 status messages.
+  * The context-menu now includes a Control-Panel menu-item on all desktops - 
previously it 
+    was Gnome-only (for tray extensions), but Xfce's tray also needs it.
+  * Light-Metering window - corrected the horizontal tick mark placement on 
the sun-plot.
+  * Light-Metering window - added enlarged tick-marks to the sun-plot at 
3,6,9,15,18,21 hours.
+  * Added option "tray-follows-theme" (default enabled) to invert the tray 
icon’s light/dark state
+    when the desktop theme changes. It can be set for trays that flip-with the 
desktop or 
+    flip-opposite to the desktop, or unset for trays don’t change at all 
(there isn't a 
+    common way to detect tray-themes, so this cannot be automated).
+  * Internal code simplifications and cleanups.
+
+-------------------------------------------------------------------

Old:
----
  vdu_controls-2.4.3.tar.gz

New:
----
  vdu_controls-2.5.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ vdu_controls.spec ++++++
--- /var/tmp/diff_new_pack.cYEAme/_old  2026-04-07 16:49:27.505903524 +0200
+++ /var/tmp/diff_new_pack.cYEAme/_new  2026-04-07 16:49:27.509903690 +0200
@@ -18,7 +18,7 @@
 
 
 Name:           vdu_controls
-Version:        2.4.3
+Version:        2.5.0
 Release:        0
 Summary:        Visual Display Unit virtual control panel
 License:        GPL-3.0-or-later

++++++ vdu_controls-2.4.3.tar.gz -> vdu_controls-2.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/PKGBUILD 
new/vdu_controls-2.5.0/PKGBUILD
--- old/vdu_controls-2.4.3/PKGBUILD     2025-08-29 04:06:52.000000000 +0200
+++ new/vdu_controls-2.5.0/PKGBUILD     2026-04-06 22:33:45.000000000 +0200
@@ -1,5 +1,5 @@
 pkgname=vdu_controls
-pkgver=2.4.3
+pkgver=2.5.0
 pkgrel=1
 pkgdesc="Visual Display Unit virtual control panel"
 arch=('i686' 'x86_64')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/README.md 
new/vdu_controls-2.5.0/README.md
--- old/vdu_controls-2.4.3/README.md    2025-08-29 04:06:52.000000000 +0200
+++ new/vdu_controls-2.5.0/README.md    2026-04-06 22:33:45.000000000 +0200
@@ -11,7 +11,8 @@
 
 Description
 -----------
-![Custom](screen-shots/ambient-slider-example.png) 
+
+<img src="screen-shots/ambient-slider-example.png" alt="vdu_controls v2.5" 
width="580">
 
 ``vdu_controls`` is a virtual control panel for external Visual Display Units 
 (VDUs, monitors, displays). It supports displays connected via DisplayPort, 
@@ -36,10 +37,10 @@
 In versions >= 2.4, the _ambient-light-level_ slider has been combined with an 
 estimate of local solar-illumination to achieve **semi-automatic brightness 
 control** throughout the day. Adjusting the slider sets the ratio between 
-indoor-illumination and outdoor solar-illumination. Should circumstances 
change, 
-adjusting the slider updates the ratio.  See the 
-[2.4 release 
notes](https://github.com/digitaltrails/vdu_controls/releases/tag/v2.4.0) 
-for a brief tutorial.
+indoor-illumination and outdoor solar-illumination. Once the ratio is set,
+it is used to automatically update brighness as the day proceeds. 
+Should the cloud-cover or weather change, adjusting the slider revises the 
ratio.  
+See the [2.4 release 
notes](https://github.com/digitaltrails/vdu_controls/releases/tag/v2.4.0) for a 
brief tutorial.
 (Solar-illumination is estimated for a  location by using the local date-time 
 to determine a sun-angle, and from
 that estimates for illumination, and air-mass.)
@@ -58,6 +59,9 @@
 to light/dark theme changes.  (For desktops that don't integrate with Qt/KDE 
themeing, 
 the `qt5ct` and `qt6ct` utilities may be used to alter the overall Qt theme.)
 
+The main-toolbar may be dragged to either the top or bottom of the main-window.
+The toolbar's location persists across restarts.
+
 From any application window, use **F1** to access **help**, and **F10** to 
access the context-menu.   The 
 **Context Menu** is also available via the right-mouse button in the 
main-window, the hamburger-menu item 
 on the bottom right of the main window, and the right-mouse button on the 
system-tray icon. The 
@@ -69,10 +73,15 @@
 > 
 > Within the UI, **tool-tips** are often available when hovering over UI 
 > components.
 
+
 ![Default](screen-shots/Screenshot_Large-330.png)  
![Custom](screen-shots/Screenshot_Small-227.png) 
 ![Custom](screen-shots/Screenshot_tray-200.png) 
![Custom](screen-shots/Screenshot_settings-300.png)
 ![Custom](screen-shots/presets.png) ![Custom](screen-shots/lux-profiles.png)
 
+> [!NOTE]
+> Several language translations are provided, but with no apparent demand, 
they 
+> are currently unmaintained and will be updated on request.
+
 #### Technical background
 
 Historically, there was little need to frequently adjust display brightness.
@@ -340,7 +349,7 @@
 * Denilson Sá Maia ([denilsonsa](https://github.com/denilsonsa)), for many 
suggestions, assistance, and contributions.
 * Matthew Coleman ([crashmatt](https://github.com/crashmatt)), Mark Lowne 
([lowne](https://github.com/lowne)), [usr3](https://github.com/usr3),
   Mateo Bohorquez G. ([Milor123](https://github.com/Milor123)), Andrew Sun 
([apsun](https://github.com/apsun)), 
-  Extent ([Extent421](https://github.com/Extent421)), Niklas Hambüchen 
([nh2](https://github.com/nh2))
+  Extent ([Extent421](https://github.com/Extent421)), Niklas Hambüchen 
([nh2](https://github.com/nh2)), Doron Behar 
([doronbehar](https://github.com/doronbehar))
   for contributing fixes to code and documentation.
 * [Jakeler](https://github.com/Jakeler), [kupiqu](https://github.com/kupiqu), 
Mateo Bohorquez ([Milor123](https://github.com/Milor123)), Johan Grande 
([nahoj](https://github.com/nahoj)), 
   [0xCUBE](https://github.com/0xCUB3), 
[RokeJulianLockhart](https://github.com/RokeJulianLockhart), 
[abil76](https://github.com/abil76), Andrew Sun 
([apsun](https://github.com/apsun))
@@ -348,6 +357,7 @@
 * Malcolm Lewis ([malcolmlewis](https://github.com/malcolmlewis)) for 
assistance with the OpenSUSE Open Build Service submissions.
 * Christopher Laws ([claws](https://github.com/claws)) for the [BH1750 
library](https://github.com/claws/BH1750) 
   and [example build](https://github.com/claws/BH1750#example) (lux-metering).
+* Viktor Sharga ([ViktorSharga](https://github.com/ViktorSharga)) for 
assisting with UI enhancements.
 * Plus others who have supplied feedback and suggestions.
 * E. Elvegård and G. Sjöstedt, "The Calculation of Illumination from Sun and 
Sky," _Illuminating Engineering_, Apr. 1940.
   [Illuminating Engineering Society, 100 Significant 
Papers](https://www.ies.org/research/publications/100-significant-papers/)
@@ -359,6 +369,26 @@
 
 Version History
 ---------------
+* 2.5.0
+  * Visual refresh of the Main-panel. Inspired by [a recent 
fork](https://github.com/ViktorSharga/vdu_controls_vibecodedUI) 
+    by @ViktorSharga.
+  * Added option "toolbar-at-top" to configure the top/bottom placement of the 
toolbar 
+    in the main-window. Top placement is more Plasma-6-like and may also be 
useful
+    when combined with top-located system-trays 
+  * Added option "separate-status-bar" to allow the main-window's status-bar 
to be 
+    separated from its toolbar.  This may be useful when combined with 
"toolbar-at-top".
+  * Replaced QProgressBar with a more modern circular busy-spinner.
+  * Added a tooltip to the status-bar that shows the last 10 status messages.
+  * The context-menu now includes a Control-Panel menu-item on all desktops - 
previously it 
+    was Gnome-only (for tray extensions), but Xfce's tray also needs it.
+  * Light-Metering window - corrected the horizontal tick mark placement on 
the sun-plot.
+  * Light-Metering window - added enlarged tick-marks to the sun-plot at 
3,6,9,15,18,21 hours.
+  * Added option "tray-follows-theme" (default enabled) to invert the tray 
icon’s light/dark state
+    when the desktop theme changes. It can be set for trays that flip-with the 
desktop or 
+    flip-opposite to the desktop, or unset for trays don’t change at all 
(there isn't a 
+    common way to detect tray-themes, so this cannot be automated).
+  * Internal code simplifications and cleanups.
+  
 * 2.4.3
   * Fix a rare TypeError when light metering.
   * Some code cleanups for the splash screen.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/docs/_build/man/vdu_controls.1 
new/vdu_controls-2.5.0/docs/_build/man/vdu_controls.1
--- old/vdu_controls-2.4.3/docs/_build/man/vdu_controls.1       2025-08-29 
04:06:52.000000000 +0200
+++ new/vdu_controls-2.5.0/docs/_build/man/vdu_controls.1       2026-04-06 
22:33:45.000000000 +0200
@@ -1,4 +1,5 @@
-.\" Man page generated from reStructuredText.
+.\" Man page generated from reStructuredText
+.\" by the Docutils 0.22.3 manpage writer.
 .
 .
 .nr rst2man-indent-level 0
@@ -27,9 +28,9 @@
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "VDU_CONTROLS" "1" "Jul 10, 2025" "" "vdu_controls"
+.TH "VDU_CONTROLS" "1" "Apr 02, 2026" "" "vdu_controls"
 .SH NAME
-vdu_controls \- vdu_controls 2.4.1
+vdu_controls \- vdu_controls 2.5.0
 .SH VDU_CONTROLS - A DDC CONTROL PANEL FOR MONITORS
 .sp
 A control panel for DisplayPort, DVI, HDMI, or USB\-connected VDUs (\fIVisual 
Display Units\fP).
@@ -47,6 +48,9 @@
 [\-\-hide\-on\-focus\-out|\-\-no\-hide\-on\-focus\-out]
 [\-\-smart\-window|\-\-no\-smart\-window] 
[\-smart\-uses\-xwayland|\-smart\-uses\-xwayland]
 [\-\-monochrome\-tray|\-\-no\-monochrome\-tray] 
[\-\-mono\-light\-tray|\-\-no\-mono\-light\-tray]
+[\-\-tray\-follows\-theme|\-\-no\-tray\-follows\-theme]
+[\-\-toolbar\-at\-top|\-no\-toolbar\-at\-top]
+[\-\-separate\-status\-bar|\-\-separate\-status\-bar]
 [\-\-protect\-nvram|\-\-no\-protect\-nvram]
 [\-\-lux\-options|\-\-no\-lux\-options]
 [\-\-schedule|\-\-no\-schedule] [\-\-weather|\-\-no\-weather]
@@ -114,6 +118,18 @@
 monochrome themed system\-tray.
 \fB\-\-no\-mono\-light\-tray\fP is the default.
 .TP
+.B \-\-tray\-follows\-theme|\-\-no\-tray\-follows\-theme
+the tray\-theme toggles between light/dark when the desktop\-theme changes
+\fB\-\-tray\-follows\-theme\fP is the default.
+.TP
+.B \-\-toolbar\-at\-top|\-\-no\-toolbar\-at\-top
+locate the toolbar at the top or bottom of the main window
+\fB\-\-no\-toolbar\-at\-top\fP is the default
+.TP
+.B \-\-separate\-status\-bar|\-\-no\-separate\-status\-bar
+separate the status\-bar from the toolbar
+\fB\-\-no\-separate\-status\-bar\fP is the default
+.TP
 .B \-\-protect\-nvram|\-\-no\-protect\-nvram
 alter options and defaults to minimize VDU NVRAM writes.
 .TP
@@ -224,7 +240,11 @@
 main\-window or the system\-tray icon.  The main\-menu has \fIALT\-key\fP 
shortcuts for all menu items
 (subject to sufficient letters being available to distinguish all user defined 
presets).
 .sp
-For further information, including screenshots, see \X'tty: link 
https://github.com/digitaltrails/vdu_controls'\fI\%https://github.com/digitaltrails/vdu_controls\fP\X'tty:
 link' .
+The main\-toolbar includes a stealthy\-drag\-handle at extreme\-left.  The 
toolbar
+can be dragged and docked at either the top or bottom top of the main\-window.
+The toolbar\(aqs position persists across restarts.
+.sp
+For further information, including screenshots, see 
\%<https://\:github\:.com/\:digitaltrails/\:vdu_controls> .
 .sp
 The long\-term effects of repeatably rewriting a VDUs setting are not well 
understood, but some
 concerns have been expressed. See \fBLIMITATIONS\fP for further details.
@@ -539,10 +559,10 @@
 .SS Presets \- supplementary weather requirements
 .sp
 A solar elevation trigger can have a weather requirement which will be checked 
against the weather
-reported by \X'tty: link https://wttr.in'\fI\%https://wttr.in\fP\X'tty: 
link'\&.
+reported by \%<https://\:wttr\:.in>\&.
 .sp
 By default, there are three possible weather requirements: \fBgood\fP, 
\fBbad\fP, and \fBall weather\fP\&.
-Each  requirement is defined by a file containing a list of WWO (\X'tty: link 
https://www.worldweatheronline.com'\fI\%https://www.worldweatheronline.com\fP\X'tty:
 link')
+Each  requirement is defined by a file containing a list of WWO 
(\%<https://\:www\:.worldweatheronline\:.com>)
 weather codes, one per line.  The three default requirements are contained in 
the files
 \fB$HOME/.config/vdu_controls/{good,bad,all}.weather\fP\&.  Additional weather 
requirements can be
 created by using a text editor to create further files.  The \fBall.weather\fP 
file exists primarily
@@ -557,7 +577,7 @@
 enhanced to fill out a place\-name for you.  Should \fBwttr.in\fP not 
recognize a place\-name, the
 place\-name can be manually edited to something more suitable. The nearest big 
city or an
 airport\-code will do, for example: LHR, LAX, JFK.  You can use a web browser 
to test a place\-name,
-for example: \X'tty: link 
https://wttr.in/JFK'\fI\%https://wttr.in/JFK\fP\X'tty: link'
+for example: \%<https://\:wttr\:.in/\:JFK>
 .sp
 When weather requirements are in use, \fBvdu_controls\fP will check that the 
coordinates in
 \fBSettings\fP \fBLocation\fP are a reasonable match for those returned from 
\fBwttr.in\fP, a warning will
@@ -662,7 +682,7 @@
 programming an Arduino with a GY\-30/BH1750, can be found at:
 .INDENT 0.0
 .INDENT 3.5
-\X'tty: link 
https://github.com/digitaltrails/vdu_controls/blob/master/Lux-metering.md'\fI\%https://github.com/digitaltrails/vdu_controls/blob/master/Lux\-metering.md\fP\X'tty:
 link'
+\%<https://\:github\:.com/\:digitaltrails/\:vdu_controls/\:blob/\:master/\:Lux-metering\:.md>
 .UNINDENT
 .UNINDENT
 .sp
@@ -671,7 +691,7 @@
 location:
 .INDENT 0.0
 .INDENT 3.5
-\X'tty: link 
https://github.com/digitaltrails/vdu_controls/tree/master/sample-scripts'\fI\%https://github.com/digitaltrails/vdu_controls/tree/master/sample\-scripts\fP\X'tty:
 link'\&.
+\%<https://\:github\:.com/\:digitaltrails/\:vdu_controls/\:tree/\:master/\:sample-scripts>\&.
 .UNINDENT
 .UNINDENT
 .sp
@@ -801,8 +821,9 @@
 Reducing the number of enabled controls can speed up initialization, decrease 
the refresh time, and
 reduce the time taken to restore presets.
 .sp
-There\(aqs plenty of useful info for getting the best out of \fBddcutil\fP at 
\X'tty: link https://www.ddcutil.com/'\fI\%https://www.ddcutil.com/\fP\X'tty: 
link'\&.
+There\(aqs plenty of useful info for getting the best out of \fBddcutil\fP at 
\%<https://\:www\:.ddcutil\:.com/>\&.
 .SH LIMITATIONS
+.SS Possible impact on VDU lifespan
 .sp
 Repeatably altering VDU settings might affect VDU lifespan, exhausting the 
NVRAM write
 cycles, stressing the VDU power\-supply, or increasing panel burn\-in.
@@ -857,6 +878,42 @@
 The bottom of the About\-dialog shows the same numbers. They update 
dynamically.
 .UNINDENT
 .UNINDENT
+.SS Cross\-platform differences
+.sp
+The UI attempts to step around minor differences between KDE, GNOME, and the 
rest,
+the UI on each may not be exactly the same.
+.sp
+Depending on which desktop or system\-tray\-extension you are using, a
+left\-mouse\-click on the app\-icon in the system\-tray may restore
+the application\(aqs main\-widow or it may bring up the the application\(aqs
+context\-menu.  To support both kinds of desktop, the context\-menu includes a
+a _Control 
+.nf
+Panel_
+.fi
+  menu option that toggles visibility of the main window.
+.sp
+Wayland doesn\(aqt allow an application to precisely position its windows.  
When the
+\fBsmart\-window\fP option is enabled and the desktop platform is Wayland, the
+application switches its platform to XWayland (X11 xcb).
+.sp
+The scaling and appearance of Qt6 differs from Qt5, its more chunky and 
rounded.  If you
+have Qt5 installed and prefer it, you can uncheck prefer\-qt6 in settings.
+.SS Desktop Theming
+.sp
+Achieving desktop neutrality comes at the price of the application not being
+fully aware or compliant with the theming conventions of any particular 
desktop.
+.sp
+For some desktops, Qt can detect in\-session theme changes, such as the change 
from
+a day\-theme to a night\-theme, and the application can respond appropriately. 
 For
+desktops where theme changes aren\(aqt detected, the application can only 
conform
+to the theme detected at startup.
+.sp
+In some cases, the system\-tray or dock theming may contrast with the theming
+applied to windows.  There isn\(aqt a straight forward Qt mechanism to discover
+whether a tray or dock is differently themed. As a result the application 
includes
+several manual settings that can alter the tray/dock icon theming between
+colored, monochrome\-dark and monochrome\-light.
 .SS Laptops
 .sp
 A laptop\(aqs builtin\-panel normally doesn\(aqt implement DDC and cannot be 
controlled
@@ -866,17 +923,6 @@
 option and provide \fBvdu_controls\fP with a ddcutil\-like script for getting 
and
 setting the panel brightness; then \fBvdu_controls\fP will treat the laptop 
panel
 just like any other VDU.  A template script is provided in the 
\fBsample\-scripts\fP\&.
-.SS Cross\-platform differences
-.sp
-Wayland doesn\(aqt allow an application to precisely position its windows.  
When the
-\fBsmart\-window\fP option is enabled and the desktop platform is Wayland, the
-application switches its platform to XWayland (X11 xcb).
-.sp
-The UI attempts to step around minor differences between KDE, GNOME, and the 
rest,
-the UI on each may not be exactly the same.
-.sp
-The scaling and appearance of Qt6 differs from Qt5, its more chunky and 
rounded.  If you
-have Qt5 installed and prefer it, you can uncheck prefer\-qt6 in settings.
 .SS Other concerns
 .sp
 The power\-supplies in some older VDUs may buzz/squeel audibly when the 
brightness is
@@ -958,14 +1004,14 @@
 .UNINDENT
 .sp
 Read ddcutil documentation concerning config of i2c_dev with nvidia GPUs. 
Detailed ddcutil info
-at \X'tty: link 
https://www.ddcutil.com/'\fI\%https://www.ddcutil.com/\fP\X'tty: link'
+at \%<https://\:www\:.ddcutil\:.com/>
 .SH ENVIRONMENT
 .INDENT 0.0
 .INDENT 3.5
 .INDENT 0.0
 .TP
 .B LC_ALL, LANG, LANGUAGE
-These  variables specify the locale for language translations and units of 
distance.
+These variables specify the locale for language translations and units of 
distance.
 LC_ALL is used by python, LANGUAGE is used by Qt. Normally, they should all 
have the same
 value, for example, \fBDa_DK\fP\&. For these to have any effect on language, 
\fBSettings\fP
 \fBTranslations Enabled\fP must also be enabled.
@@ -1026,7 +1072,7 @@
 .UNINDENT
 .SH REPORTING BUGS
 .sp
-\X'tty: link 
https://github.com/digitaltrails/vdu_controls/issues'\fI\%https://github.com/digitaltrails/vdu_controls/issues\fP\X'tty:
 link'
+\%<https://\:github\:.com/\:digitaltrails/\:vdu_controls/\:issues>
 .SH GNU LICENSE
 .sp
 This program is free software: you can redistribute it and/or modify it
@@ -1039,10 +1085,9 @@
 more details.
 .sp
 You should have received a copy of the GNU General Public License along
-with this program. If not, see \X'tty: link 
https://www.gnu.org/licenses/'\fI\%https://www.gnu.org/licenses/\fP\X'tty: 
link'\&.
-.SH AUTHOR
+with this program. If not, see \%<https://\:www\:.gnu\:.org/\:licenses/>\&.
+.SH Author
 Michael Hamilton
-.SH COPYRIGHT
+.SH Copyright
 2021, Michael Hamilton
-.\" Generated by docutils manpage writer.
-.
+.\" End of generated man page.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/vdu_controls-2.4.3/docs/_build/man/vdu_controls.1.html 
new/vdu_controls-2.5.0/docs/_build/man/vdu_controls.1.html
--- old/vdu_controls-2.4.3/docs/_build/man/vdu_controls.1.html  2025-08-29 
04:06:52.000000000 +0200
+++ new/vdu_controls-2.5.0/docs/_build/man/vdu_controls.1.html  2026-04-06 
22:33:45.000000000 +0200
@@ -12,6 +12,9 @@
                  [--hide-on-focus-out|--no-hide-on-focus-out]
                  [--smart-window|--no-smart-window] 
[-smart-uses-xwayland|-smart-uses-xwayland]
                  [--monochrome-tray|--no-monochrome-tray] 
[--mono-light-tray|--no-mono-light-tray]
+                 [--tray-follows-theme|--no-tray-follows-theme]
+                 [--toolbar-at-top|-no-toolbar-at-top]
+                 [--separate-status-bar|--separate-status-bar]
                  [--protect-nvram|--no-protect-nvram]
                  [--lux-options|--no-lux-options]
                  [--schedule|--no-schedule] [--weather|--no-weather]
@@ -54,6 +57,15 @@
   --mono-light-tray|--no-mono-light-tray
                         monochrome themed system-tray.
                         ``--no-mono-light-tray`` is the default.
+  --tray-follows-theme|--no-tray-follows-theme
+                        the tray-theme toggles between light/dark when the 
desktop-theme changes
+                        ``--tray-follows-theme`` is the default.
+  --toolbar-at-top|--no-toolbar-at-top
+                        locate the toolbar at the top or bottom of the main 
window
+                        ``--no-toolbar-at-top`` is the default
+  --separate-status-bar|--no-separate-status-bar
+                        separate the status-bar from the toolbar
+                        ``--no-separate-status-bar`` is the default
   --protect-nvram|--no-protect-nvram
                         alter options and defaults to minimize VDU NVRAM 
writes.
   --order-by-name|--no-order-by-name
@@ -139,6 +151,9 @@
 the main-window or the system-tray icon. The main-menu has
 <code>ALT-key</code> shortcuts for all menu items (subject to sufficient
 letters being available to distinguish all user defined presets).</p>
+<p>The main-toolbar includes a stealthy-drag-handle at extreme-left. The
+toolbar can be dragged and docked at either the top or bottom top of the
+main-window. The toolbar’s position persists across restarts.</p>
 <p>For further information, including screenshots, see
 https://github.com/digitaltrails/vdu_controls .</p>
 <p>The long-term effects of repeatably rewriting a VDUs setting are not
@@ -645,6 +660,8 @@
 <p>There’s plenty of useful info for getting the best out of
 <code>ddcutil</code> at https://www.ddcutil.com/.</p>
 <h1 id="limitations">Limitations</h1>
+<h2 id="possible-impact-on-vdu-lifespan">Possible impact on VDU
+lifespan</h2>
 <p>Repeatably altering VDU settings might affect VDU lifespan,
 exhausting the NVRAM write cycles, stressing the VDU power-supply, or
 increasing panel burn-in.</p>
@@ -683,6 +700,36 @@
 dynamically.</li>
 </ul></li>
 </ul>
+<h2 id="cross-platform-differences">Cross-platform differences</h2>
+<p>The UI attempts to step around minor differences between KDE, GNOME,
+and the rest, the UI on each may not be exactly the same.</p>
+<p>Depending on which desktop or system-tray-extension you are using, a
+left-mouse-click on the app-icon in the system-tray may restore the
+application’s main-widow or it may bring up the the application’s
+context-menu. To support both kinds of desktop, the context-menu
+includes a a <em>Control Panel</em> menu option that toggles visibility
+of the main window.</p>
+<p>Wayland doesn’t allow an application to precisely position its
+windows. When the <code>smart-window</code> option is enabled and the
+desktop platform is Wayland, the application switches its platform to
+XWayland (X11 xcb).</p>
+<p>The scaling and appearance of Qt6 differs from Qt5, its more chunky
+and rounded. If you have Qt5 installed and prefer it, you can uncheck
+prefer-qt6 in settings.</p>
+<h2 id="desktop-theming">Desktop Theming</h2>
+<p>Achieving desktop neutrality comes at the price of the application
+not being fully aware or compliant with the theming conventions of any
+particular desktop.</p>
+<p>For some desktops, Qt can detect in-session theme changes, such as
+the change from a day-theme to a night-theme, and the application can
+respond appropriately. For desktops where theme changes aren’t detected,
+the application can only conform to the theme detected at startup.</p>
+<p>In some cases, the system-tray or dock theming may contrast with the
+theming applied to windows. There isn’t a straight forward Qt mechanism
+to discover whether a tray or dock is differently themed. As a result
+the application includes several manual settings that can alter the
+tray/dock icon theming between colored, monochrome-dark and
+monochrome-light.</p>
 <h2 id="laptops">Laptops</h2>
 <p>A laptop’s builtin-panel normally doesn’t implement DDC and cannot be
 controlled by <code>ddcutil</code> or <code>ddcutil-service</code>.
@@ -693,16 +740,6 @@
 and setting the panel brightness; then <code>vdu_controls</code> will
 treat the laptop panel just like any other VDU. A template script is
 provided in the <code>sample-scripts</code>.</p>
-<h2 id="cross-platform-differences">Cross-platform differences</h2>
-<p>Wayland doesn’t allow an application to precisely position its
-windows. When the <code>smart-window</code> option is enabled and the
-desktop platform is Wayland, the application switches its platform to
-XWayland (X11 xcb).</p>
-<p>The UI attempts to step around minor differences between KDE, GNOME,
-and the rest, the UI on each may not be exactly the same.</p>
-<p>The scaling and appearance of Qt6 differs from Qt5, its more chunky
-and rounded. If you have Qt5 installed and prefer it, you can uncheck
-prefer-qt6 in settings.</p>
 <h2 id="other-concerns">Other concerns</h2>
 <p>The power-supplies in some older VDUs may buzz/squeel audibly when
 the brightness is turned way down. This may not be a major issue
@@ -767,7 +804,7 @@
 GPUs. Detailed ddcutil info at https://www.ddcutil.com/</p>
 <h1 id="environment">Environment</h1>
 <pre><code>LC_ALL, LANG, LANGUAGE
-    These  variables specify the locale for language translations and units of 
distance.
+    These variables specify the locale for language translations and units of 
distance.
     LC_ALL is used by python, LANGUAGE is used by Qt. Normally, they should 
all have the same
     value, for example, ``Da_DK``. For these to have any effect on language, 
``Settings``
     ``Translations Enabled`` must also be enabled.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/docs/conf.py 
new/vdu_controls-2.5.0/docs/conf.py
--- old/vdu_controls-2.4.3/docs/conf.py 2025-08-29 04:06:52.000000000 +0200
+++ new/vdu_controls-2.5.0/docs/conf.py 2026-04-06 22:33:45.000000000 +0200
@@ -24,7 +24,7 @@
 author = 'Michael Hamilton'
 
 # The full version, including alpha/beta/rc tags
-release = '2.4.3'
+release = '2.5.0'
 
 
 # -- General configuration ---------------------------------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/icons/vdu-power-on.svg 
new/vdu_controls-2.5.0/icons/vdu-power-on.svg
--- old/vdu_controls-2.4.3/icons/vdu-power-on.svg       1970-01-01 
01:00:00.000000000 +0100
+++ new/vdu_controls-2.5.0/icons/vdu-power-on.svg       2026-04-06 
22:33:45.000000000 +0200
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 Michael Hamilton License Creative Commons - Attribution CC 
BY -->
+<svg viewBox="0 0 24 24" width="24" height="24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+    <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
+    <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round"  
stroke-width="2" transform="">
+        <path fill="None" d="M14 12 A 5 5 0 1 0 20 12 M 17 11 L 17 16.5 M 9 20 
L 1 20 1 5 20 5 20 8"/>
+    </g>
+</svg>
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/icons/vdu_ambient.svg 
new/vdu_controls-2.5.0/icons/vdu_ambient.svg
--- old/vdu_controls-2.4.3/icons/vdu_ambient.svg        1970-01-01 
01:00:00.000000000 +0100
+++ new/vdu_controls-2.5.0/icons/vdu_ambient.svg        2026-04-06 
22:33:45.000000000 +0200
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 Michael Hamilton License Creative Commons - Attribution CC 
BY -->
+<svg viewBox="0 0 24 24" width="24" height="24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+  <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
+    <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round" 
stroke-width="2">
+        <path fill="none" d="M9 20 L1 20 1 5 20 5 20 7" />
+        <circle cx="17" cy="16" r="5" stroke="currentColor" fill="none" />
+        <rect x="11" y="21.5" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="8.5" y="15.5" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="10.5" y="10" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="15.5" y="7.5" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="21.5" y="9" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+    </g>
+</svg>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/icons/vdu_connected.svg 
new/vdu_controls-2.5.0/icons/vdu_connected.svg
--- old/vdu_controls-2.4.3/icons/vdu_connected.svg      1970-01-01 
01:00:00.000000000 +0100
+++ new/vdu_controls-2.5.0/icons/vdu_connected.svg      2026-04-06 
22:33:45.000000000 +0200
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 Michael Hamilton License Creative Commons - Attribution CC 
BY -->
+<svg viewBox="0 0 24 24" width="24" height="24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+    <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
+    <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round"  
stroke-width="2" transform="">
+        <path fill="None" d="M 20 18 L 1 18 1 5 20 5 20 18 M 6.5 21 L 15 21"/>
+    </g>
+</svg>
\ No newline at end of file
Binary files old/vdu_controls-2.4.3/screen-shots/ambient-slider-example.png and 
new/vdu_controls-2.5.0/screen-shots/ambient-slider-example.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/setup.cfg 
new/vdu_controls-2.5.0/setup.cfg
--- old/vdu_controls-2.4.3/setup.cfg    2025-08-29 04:06:52.000000000 +0200
+++ new/vdu_controls-2.5.0/setup.cfg    2026-04-06 22:33:45.000000000 +0200
@@ -1,6 +1,6 @@
 [metadata]
 name = vdu_controls-digitaltrails
-version = 2.4.3
+version = 2.5.0
 author = Michael Hamilton
 author_email = [email protected]
 description = A GUI for controlling Visual Display Units
@@ -15,6 +15,9 @@
     Operating System :: POSIX :: Linux
 
 [options]
-scripts= vdu_controls.py,
-packages=
+py_modules = vdu_controls
 python_requires = >=3.8
+
+[options.entry_points]
+console_scripts =
+    vdu_controls = vdu_controls:main
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/vdu_controls.py 
new/vdu_controls-2.5.0/vdu_controls.py
--- old/vdu_controls-2.4.3/vdu_controls.py      2025-08-29 04:06:52.000000000 
+0200
+++ new/vdu_controls-2.5.0/vdu_controls.py      2026-04-06 22:33:45.000000000 
+0200
@@ -17,6 +17,9 @@
                      [--hide-on-focus-out|--no-hide-on-focus-out]
                      [--smart-window|--no-smart-window] 
[-smart-uses-xwayland|-smart-uses-xwayland]
                      [--monochrome-tray|--no-monochrome-tray] 
[--mono-light-tray|--no-mono-light-tray]
+                     [--tray-follows-theme|--no-tray-follows-theme]
+                     [--toolbar-at-top|-no-toolbar-at-top]
+                     [--separate-status-bar|--separate-status-bar]
                      [--protect-nvram|--no-protect-nvram]
                      [--lux-options|--no-lux-options]
                      [--schedule|--no-schedule] [--weather|--no-weather]
@@ -62,6 +65,15 @@
       --mono-light-tray|--no-mono-light-tray
                             monochrome themed system-tray.
                             ``--no-mono-light-tray`` is the default.
+      --tray-follows-theme|--no-tray-follows-theme
+                            the tray-theme toggles between light/dark when the 
desktop-theme changes
+                            ``--tray-follows-theme`` is the default.
+      --toolbar-at-top|--no-toolbar-at-top
+                            locate the toolbar at the top or bottom of the 
main window
+                            ``--no-toolbar-at-top`` is the default
+      --separate-status-bar|--no-separate-status-bar
+                            separate the status-bar from the toolbar
+                            ``--no-separate-status-bar`` is the default
       --protect-nvram|--no-protect-nvram
                             alter options and defaults to minimize VDU NVRAM 
writes.
       --order-by-name|--no-order-by-name
@@ -145,6 +157,10 @@
 main-window or the system-tray icon.  The main-menu has `ALT-key` shortcuts 
for all menu items
 (subject to sufficient letters being available to distinguish all user defined 
presets).
 
+The main-toolbar includes a stealthy-drag-handle at extreme-left.  The toolbar
+can be dragged and docked at either the top or bottom top of the main-window.
+The toolbar's position persists across restarts.
+
 For further information, including screenshots, see 
https://github.com/digitaltrails/vdu_controls .
 
 The long-term effects of repeatably rewriting a VDUs setting are not well 
understood, but some
@@ -635,6 +651,9 @@
 Limitations
 ===========
 
+Possible impact on VDU lifespan
+-------------------------------
+
 Repeatably altering VDU settings might affect VDU lifespan, exhausting the 
NVRAM write
 cycles, stressing the VDU power-supply, or increasing panel burn-in.
 
@@ -663,30 +682,52 @@
     the number of VCP (NVRAM) writes.
   + The bottom of the About-dialog shows the same numbers. They update 
dynamically.
 
-Laptops
--------
-
-A laptop's builtin-panel normally doesn't implement DDC and cannot be 
controlled
-by ``ddcutil`` or ``ddcutil-service``.  Laptop panel brightness is controlled
-by a variety of methods that vary by vendor and hardware.  If you have a laptop
-where such adjustments can be scripted, you can use the ``--ddcutil-emulator``
-option and provide ``vdu_controls`` with a ddcutil-like script for getting and
-setting the panel brightness; then ``vdu_controls`` will treat the laptop panel
-just like any other VDU.  A template script is provided in the 
``sample-scripts``.
-
 Cross-platform differences
 --------------------------
 
+The UI attempts to step around minor differences between KDE, GNOME, and the 
rest,
+the UI on each may not be exactly the same.
+
+Depending on which desktop or system-tray-extension you are using, a
+left-mouse-click on the app-icon in the system-tray may restore
+the application's main-widow or it may bring up the the application's
+context-menu.  To support both kinds of desktop, the context-menu includes a
+a _Control Panel_  menu option that toggles visibility of the main window.
+
 Wayland doesn't allow an application to precisely position its windows.  When 
the
 ``smart-window`` option is enabled and the desktop platform is Wayland, the
 application switches its platform to XWayland (X11 xcb).
 
-The UI attempts to step around minor differences between KDE, GNOME, and the 
rest,
-the UI on each may not be exactly the same.
-
 The scaling and appearance of Qt6 differs from Qt5, its more chunky and 
rounded.  If you
 have Qt5 installed and prefer it, you can uncheck prefer-qt6 in settings.
 
+Desktop Theming
+---------------
+
+Achieving desktop neutrality comes at the price of the application not being
+fully aware or compliant with the theming conventions of any particular 
desktop.
+
+For some desktops, Qt can detect in-session theme changes, such as the change 
from
+a day-theme to a night-theme, and the application can respond appropriately.  
For
+desktops where theme changes aren't detected, the application can only conform
+to the theme detected at startup.
+
+In some cases, the system-tray or dock theming may contrast with the theming
+applied to windows.  There isn't a straight forward Qt mechanism to discover
+whether a tray or dock is differently themed. As a result the application 
includes
+several manual settings that can alter the tray/dock icon theming between
+colored, monochrome-dark and monochrome-light.
+
+Laptops
+-------
+
+A laptop's builtin-panel normally doesn't implement DDC and cannot be 
controlled
+by ``ddcutil`` or ``ddcutil-service``.  Laptop panel brightness is controlled
+by a variety of methods that vary by vendor and hardware.  If you have a laptop
+where such adjustments can be scripted, you can use the ``--ddcutil-emulator``
+option and provide ``vdu_controls`` with a ddcutil-like script for getting and
+setting the panel brightness; then ``vdu_controls`` will treat the laptop panel
+just like any other VDU.  A template script is provided in the 
``sample-scripts``.
 
 Other concerns
 --------------
@@ -891,7 +932,8 @@
         if qt_version == 6:
             from PyQt6 import QtCore, QtNetwork
             from PyQt6.QtCore import Qt, QCoreApplication, QThread, 
pyqtSignal, QProcess, QPoint, QObject, QEvent, \
-                QSettings, QSize, QTimer, QTranslator, QLocale, QT_TR_NOOP, 
QVariant, pyqtSlot, QMetaType, QDir, QRegularExpression, QPointF
+                QSettings, QSize, QTimer, QTranslator, QLocale, QT_TR_NOOP, 
QVariant, pyqtSlot, QMetaType, QDir, \
+                QRegularExpression, QPointF, QRect
             from PyQt6.QtDBus import QDBusConnection, QDBusInterface, 
QDBusMessage, QDBusArgument, QDBusVariant
             from PyQt6.QtGui import QAction, QShortcut, QPixmap, QIcon, 
QCursor, QImage, QPainter, QRegularExpressionValidator, \
                 QPalette, QGuiApplication, QColor, QValidator, QPen, QFont, 
QFontMetrics, QMouseEvent, QResizeEvent, QKeySequence, QPolygon, \
@@ -899,7 +941,7 @@
             from PyQt6.QtSvg import QSvgRenderer
             from PyQt6.QtSvgWidgets import QSvgWidget
             from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, 
QHBoxLayout, QSlider, QMessageBox, QLineEdit, QLabel, \
-                QSplashScreen, QPushButton, QProgressBar, QComboBox, 
QSystemTrayIcon, QMenu, QStyle, QTextEdit, QDialog, QTabWidget, \
+                QSplashScreen, QPushButton, QComboBox, QSystemTrayIcon, QMenu, 
QStyle, QTextEdit, QDialog, QTabWidget, \
                 QCheckBox, QPlainTextEdit, QGridLayout, QSizePolicy, 
QMainWindow, QToolBar, QToolButton, QFileDialog, \
                 QWidgetItem, QScrollArea, QGroupBox, QFrame, QSplitter, 
QSpinBox, QDoubleSpinBox, QInputDialog, QStatusBar, \
                 QSpacerItem, QListWidget, QListWidgetItem
@@ -908,14 +950,15 @@
         elif qt_version == 5:  # Covers all other values.
             from PyQt5 import QtCore, QtNetwork
             from PyQt5.QtCore import Qt, QCoreApplication, QThread, 
pyqtSignal, QProcess, QPoint, QObject, QEvent, \
-                QSettings, QSize, QTimer, QTranslator, QLocale, QT_TR_NOOP, 
QVariant, pyqtSlot, QMetaType, QDir, QRegularExpression, QPointF
+                QSettings, QSize, QTimer, QTranslator, QLocale, QT_TR_NOOP, 
QVariant, pyqtSlot, QMetaType, QDir, \
+                QRegularExpression, QPointF, QRect
             from PyQt5.QtDBus import QDBusConnection, QDBusInterface, 
QDBusMessage, QDBusArgument, QDBusVariant
             from PyQt5.QtGui import QPixmap, QIcon, QCursor, QImage, QPainter, 
QRegularExpressionValidator, \
                 QPalette, QGuiApplication, QColor, QValidator, QPen, QFont, 
QFontMetrics, QMouseEvent, QResizeEvent, QKeySequence, QPolygon, \
                 QDoubleValidator
             from PyQt5.QtSvg import QSvgWidget, QSvgRenderer
             from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, 
QHBoxLayout, QSlider, QMessageBox, QLineEdit, QLabel, \
-                QSplashScreen, QPushButton, QProgressBar, QComboBox, 
QSystemTrayIcon, QMenu, QStyle, QTextEdit, QDialog, QTabWidget, \
+                QSplashScreen, QPushButton, QComboBox, QSystemTrayIcon, QMenu, 
QStyle, QTextEdit, QDialog, QTabWidget, \
                 QCheckBox, QPlainTextEdit, QGridLayout, QSizePolicy, QAction, 
QMainWindow, QToolBar, QToolButton, QFileDialog, \
                 QWidgetItem, QScrollArea, QGroupBox, QFrame, QSplitter, 
QSpinBox, QDoubleSpinBox, QInputDialog, QStatusBar, QShortcut, \
                 QSpacerItem, QListWidget, QListWidgetItem
@@ -937,10 +980,12 @@
 
 
 APPNAME = "VDU Controls"
-VDU_CONTROLS_VERSION = '2.4.3'
+VDU_CONTROLS_VERSION = '2.5.0'
 VDU_CONTROLS_VERSION_TUPLE = tuple(int(i) for i in 
VDU_CONTROLS_VERSION.split('.'))
 assert sys.version_info >= (3, 8), f'{APPNAME} utilises python version 3.8 or 
greater (your python is {sys.version}).'
 
+TOOLTIP_DURATION_MSEC = 750
+
 WESTERN_SKY = 'western-sky'
 EASTERN_SKY = 'eastern-sky'
 
@@ -969,13 +1014,14 @@
 ERROR_SYMBOL = '\u274e'  # NEGATIVE SQUARED CROSS MARK
 WARNING_SYMBOL = '\u26a0'  # WARNING SIGN
 ALMOST_EQUAL_SYMBOL = '\u2248'  # ALMOST EQUAL TO
-SMOOTHING_SYMBOL = '\u21dd'  # RIGHT POINTING SQUIGGLY ARROW
+SMOOTHING_SYMBOL = '\u219D'  # RIGHTWARDS WAVE ARROW
 STEPPING_SYMBOL = '\u279f'  # DASHED TRIANGLE-HEADED RIGHTWARDS ARROW
 RAISED_HAND_SYMBOL = '\u270b'  # RAISED HAND
 RIGHT_POINTER_WHITE = '\u25B9'  # WHITE RIGHT-POINTING SMALL TRIANGLE
 RIGHT_POINTER_BLACK = '\u25B8'  # BLACK RIGHT-POINTING SMALL TRIANGLE
 MENU_ACTIVE_PRESET_SYMBOL = '\u25c2'  # BLACK LEFT-POINTING SMALL TRIANGLE
-SET_VCP_SYMBOL = "\u25B7"  # WHITE RIGHT-POINTING TRIANGLE
+SET_VCP_SYMBOL = '\u25B7'  # WHITE RIGHT-POINTING TRIANGLE
+MESSAGE_SYMBOL = '\u23F5'   # MEDIA PLAY
 
 SolarElevationKey = namedtuple('SolarElevationKey', ['direction', 'elevation'])
 SolarElevationData = namedtuple('SolarElevationData', ['azimuth', 'zenith', 
'when'])
@@ -990,14 +1036,6 @@
     return type_id.value if isinstance(type_id, Enum) else type_id  # 
awfulness of enums in pyqt6
 
 
-def is_gnome_desktop() -> bool:
-    return os.environ.get('XDG_CURRENT_DESKTOP', default='unknown').lower() == 
'gnome'
-
-
-def is_cosmic_desktop() -> bool:
-    return os.environ.get('XDG_CURRENT_DESKTOP', default='unknown').lower() == 
'cosmic'
-
-
 def is_running_in_gui_thread() -> bool:
     return QThread.currentThread() == gui_thread
 
@@ -1134,7 +1172,8 @@
 <a 
href="https://github.com/digitaltrails/vdu_controls/releases/tag/v{VERSION}";>
 https://github.com/digitaltrails/vdu_controls/releases/tag/v{VERSION}</a>
 
<br/>___________________________________________________________________________"""
-RELEASE_INFO = QT_TR_NOOP('Bug fix release.')
+RELEASE_INFO = QT_TR_NOOP('<b>Modernity</b>: Appearance Refresh. <span 
style="font-size: 50px;">&#x1F389;</span>"'
+                          '<br/>Relocatable toolbar and status-bar - see 
Settings.')
 
 CURRENT_PRESET_NAME_FILE = CONFIG_DIR_PATH.joinpath('current_preset.txt')
 CUSTOM_TRAY_ICON_FILE = CONFIG_DIR_PATH.joinpath('tray_icon.svg')
@@ -1166,13 +1205,29 @@
 mono_light_tray = False
 MONOCHROME_APP_ICON = b"""
 <svg viewBox="0 0 22 22" version="1.1" id="svg1" 
xmlns="http://www.w3.org/2000/svg";>
-  <defs id="defs3051"><style type="text/css" 
id="current-color-scheme">.ColorScheme-Text {color:#ffffff;}</style></defs>
+  <defs id="defs3051"><style type="text/css" 
id="current-color-scheme">.ColorScheme-Text {color:#232629;}</style></defs>
   <path style="fill:currentColor;fill-opacity:1;stroke:none" 
class="ColorScheme-Text"
      d="m 3.012318,1.987629 -0.086226,13.98553 h 1 l 5.0022397,0.02464 -1e-7,2 
-2.0022396,-0.02464 v 1 h 8.0000002 v -1 
      l -2.00224,-0.01232 -0.01232,-2 5.01456,0.01232 h 1 L 18.957944,2.0296853 
17.989795,2.0050493 4.0174203,1.9774244 
      Z m 0.9927843,1.0339651 13.9651597,-0.01742 -0.01954,10.9465899 
-14.0000001,0.02464 z" id="rect4211"/>
 </svg>"""
 
+
+FALLBACK_SPLASH_SVG = b"""
+<svg viewBox="0 0 24 24" width="256" height="256" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+    <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
+    <linearGradient id="screenGradient" x1="0%" y1="0%" x2="100%" y2="100%">
+      <stop offset="0%" stop-color="#66c0f1" /> <!-- Start color (offset 0%) 
-->
+      <stop offset="100%" stop-color="#3f7eed" />  <!-- End color (offset 
100%) -->
+    </linearGradient>
+    <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round" 
stroke-width="1.2" transform="">
+        <rect x="2.5" y="3" width="19.25" height="15" 
fill="url(#screenGradient)" rx="1" ry="1"/>
+        <path fill="None" d="M 3 17.5 L 21.5 17.5 M 8.5 20.5 L 15.75 20.5"/>
+        <path stroke-width="2" stroke-linecap="square" fill="None" d="M 11 19 
L 13 19"/>
+    </g>
+</svg>"""
+
+
 # modified brightness icon from breeze5-icons: LGPL-3.0-only
 BRIGHTNESS_SVG = b"""
 <svg xmlns="http://www.w3.org/2000/svg"; version="1.1" viewBox="0 0 24 24" 
width="24" height="24">
@@ -1263,11 +1318,35 @@
 <svg viewBox="0 0 24 24" width="24" height="24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
     <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
     <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round"  
stroke-width="2" transform="">
+        <path fill="None" d="M 20 18 L 1 18 1 5 20 5 20 18 M 6.5 21 L 15 21"/>
+    </g>
+</svg>
+"""
+
+VDU_POWER_ON_ICON_SOURCE = b"""
+<svg viewBox="0 0 24 24" width="24" height="24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+    <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
+    <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round"  
stroke-width="2" transform="">
         <path fill="None" d="M14 12 A 5 5 0 1 0 20 12 M 17 11 L 17 16.5 M 9 20 
L 1 20 1 5 20 5 20 8"/>
     </g>
 </svg>
 """
 
+AMBIENT_PANEL_ICON_SOURCE = b"""
+<svg viewBox="0 0 24 24" width="24" height="24" fill="none" 
xmlns="http://www.w3.org/2000/svg";>
+  <style type="text/css" id="current-color-scheme"> .ColorScheme-Text { 
color:#232629; } </style>
+    <g class="ColorScheme-Text" stroke="currentColor" stroke-linecap="round" 
stroke-width="2">
+        <path fill="none" d="M9 20 L1 20 1 5 20 5 20 7" />
+        <circle cx="17" cy="16" r="5" stroke="currentColor" fill="none" />
+        <rect x="11" y="21.5" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="8.5" y="15.5" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="10.5" y="10" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="15.5" y="7.5" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+        <rect x="21.5" y="9" width="1" height="1" rx="5" ry="5" 
stroke-width="1" fill="currentColor" />
+    </g>
+</svg>
+"""
+
 # view-refresh icon from breeze5-icons: LGPL-3.0-only
 REFRESH_ICON_SOURCE = b"""
 <svg xmlns="http://www.w3.org/2000/svg"; version="1.1" viewBox="0 0 24 24" 
width="24" height="24">
@@ -1402,14 +1481,6 @@
     return ThemeType.POLYCHROME_DARK if is_dark_theme() else 
ThemeType.POLYCHROME_LIGHT
 
 
-def get_tray_theme_type(main_config: VduControlsConfig):
-    if main_config.is_set(ConfOpt.MONO_LIGHT_TRAY_ENABLED):
-        return ThemeType.MONOCHROME_LIGHT
-    if main_config.is_set(ConfOpt.MONOCHROME_TRAY_ENABLED):
-        return ThemeType.MONOCHROME_DARK
-    return ThemeType.UNTHEMED  # Don't alter colors for overlay onto app icon 
in tray
-
-
 DEVELOPERS_NATIVE_FONT_HEIGHT = 32  # The font height in physical pixels being 
used on my development desktop.
 native_font_height_pixels: int | None = None  # A metric for use in sizing 
components relative to DEVELOPERS_NATIVE_FONT_HEIGHT.
 
@@ -1427,13 +1498,12 @@
 
 
 def get_splash_image() -> QPixmap:
-    """Get the splash pixmap from the installed png, failing that, the 
internal splash png."""
-    pixmap = QPixmap()
+    """Get the splash pixmap from the installed png, failing that, the 
internal splash svg."""
     if os.path.isfile(DEFAULT_SPLASH_PNG) and os.access(DEFAULT_SPLASH_PNG, 
os.R_OK):
+        pixmap = QPixmap()
         pixmap.load(DEFAULT_SPLASH_PNG)
-    else:
-        pixmap.loadFromData(base64.decodebytes(FALLBACK_SPLASH_PNG_BASE64), 
'PNG')
-    return pixmap
+        return pixmap
+    return create_pixmap_from_svg_bytes(FALLBACK_SPLASH_SVG, 256, 256)
 
 
 def clamp(v: int, min_v: int, max_v: int) -> int:
@@ -2530,6 +2600,12 @@
                                    tip=QT_TR_NOOP('monochrome dark themed 
system tray'))
     MONO_LIGHT_TRAY_ENABLED = 
_def(cname=QT_TR_NOOP('mono-light-tray-enabled'), default="no", restart=False,
                                    tip=QT_TR_NOOP('monochrome light themed 
system tray'))
+    TRAY_FOLLOWS_THEME = _def(cname=QT_TR_NOOP('tray-follows-theme'), 
default="yes", restart=False,
+                              tip=QT_TR_NOOP('tray dark/light theming follows 
desktop-theme changes'))
+    TOOLBAR_AT_TOP = _def(cname=QT_TR_NOOP('toolbar-at-top'), default="no", 
restart=False,
+                              tip=QT_TR_NOOP('toolbar resides at top of main 
window'))
+    SEPARATE_STATUS_BAR = _def(cname=QT_TR_NOOP('separate-status-bar'), 
default="no", restart=True,
+                              tip=QT_TR_NOOP('seperate the status-bar from the 
tool-bar'))
     PROTECT_NVRAM_ENABLED = _def(cname=QT_TR_NOOP('protect-nvram'), 
default="yes", restart=True,
                                  tip=QT_TR_NOOP('alter options and defaults to 
minimize VDU NVRAM writes'))
     ORDER_BY_NAME = _def(cname=QT_TR_NOOP('order-by-name'), default="no",
@@ -3090,7 +3166,35 @@
 class SubWinDialog(QDialog):  # Fix for gnome: QDialog must be a subwindow, 
otherwise it will always stay on top of other windows.
 
     def __init__(self, parent: QWidget | None = None) -> None:
-        super().__init__(parent, Qt.WindowType.SubWindow if is_gnome_desktop() 
else Qt.WindowType.Window)
+        super().__init__(parent, Qt.WindowType.SubWindow)
+
+
+
+class IconLabel(QWidget):
+
+    def __init__(self, icon_source: bytes, main_text: str, sub_text) -> None:
+        super().__init__()
+        layout = QHBoxLayout()
+        layout.setAlignment(Qt.AlignmentFlag.AlignTop)
+        self.setLayout(layout)
+        self.svg_icon: QSvgWidget | None = None
+        self.icon_source = icon_source
+        svg_icon = QSvgWidget()
+        svg_icon.load(handle_theme(icon_source, polychrome_light_or_dark()))
+        svg_icon.setFixedSize(native_font_height(scaled=1.8), 
native_font_height(scaled=1.8))
+        self.svg_icon = svg_icon
+        layout.addWidget(svg_icon)
+        self.label = QLabel(f"<span style='font-weight:bold;'>{main_text}<br/>"
+                            f"<span 
style='font-size:{native_font_height(0.5)}px; 
font-weight:normal;'>{sub_text}</span>")
+        self.label.setTextFormat(Qt.TextFormat.RichText)
+        self.label.setWordWrap(True)
+        layout.addWidget(self.label)
+
+    def event(self, event: QEvent | None) -> bool:
+        if event and event.type() == QEvent.Type.PaletteChange:  # 
PalletChange happens after the new style sheet is in use.
+            self.svg_icon.load(handle_theme(self.icon_source, 
polychrome_light_or_dark()))
+        return super().event(event)
+
 
 
 class StdButton(QPushButton):  # Reduce some repetitiveness in the code
@@ -3723,12 +3827,13 @@
         layout = QHBoxLayout()
         self.setLayout(layout)
         self.svg_icon: QSvgWidget | None = None
+        self.setToolTip(tr(vcp_capability.name))
+        self.setToolTipDuration(TOOLTIP_DURATION_MSEC)
         if (vcp_capability.vcp_code in SUPPORTED_VCP_BY_CODE
                 and SUPPORTED_VCP_BY_CODE[vcp_capability.vcp_code].icon_source 
is not None):
             svg_icon = QSvgWidget()
             
svg_icon.load(handle_theme(SUPPORTED_VCP_BY_CODE[vcp_capability.vcp_code].icon_source,
 polychrome_light_or_dark()))
             svg_icon.setFixedSize(native_font_height(scaled=1.8), 
native_font_height(scaled=1.8))
-            svg_icon.setToolTip(vcp_capability.translated_name())
             self.svg_icon = svg_icon
             layout.addWidget(svg_icon)
         else:
@@ -3800,6 +3905,8 @@
         layout.addWidget(QLabel(self.translate_label(vcp_capability.name)))
         self.combo_box = combo_box = QComboBox()
         layout.addWidget(combo_box)
+        self.setToolTip(tr(vcp_capability.name))
+        self.setToolTipDuration(TOOLTIP_DURATION_MSEC)
 
         self.keys = []
         for value, desc in self.vcp_capability.values:
@@ -3855,7 +3962,9 @@
     def __init__(self, controller: VduController, vdu_exception_handler: 
Callable) -> None:
         super().__init__()
         layout = QVBoxLayout()
-        self.label = QLabel(tr('Monitor {}: {}').format(controller.vdu_number, 
controller.get_vdu_preferred_name()))
+        create_icon_from_svg_bytes(VDU_CONNECTED_ICON_SOURCE)
+        self.label = IconLabel(VDU_CONNECTED_ICON_SOURCE,
+                               controller.get_vdu_preferred_name(), 
tr("Monitor {}".format(controller.vdu_number)))
         layout.addWidget(self.label)
         self.controller: VduController = controller
         self.vcp_controls: List[VduControlBase] = []
@@ -3881,6 +3990,11 @@
                 layout.addWidget(control)
                 self.vcp_controls.append(control)
 
+        line = QFrame()
+        line.setFrameShape(QFrame.Shape.HLine)
+        line.setFrameShadow(QFrame.Shadow.Sunken)
+        layout.addWidget(line)
+
         if len(self.vcp_controls) != 0:
             self.setLayout(layout)
 
@@ -3919,8 +4033,9 @@
 
     def update_stats(self):
         name, sid = self.controller.get_vdu_preferred_name(), 
self.controller.vdu_stable_id
-        self.label.setToolTip(tr("{}\nSet-VCP writes: {}").format(sid if id == 
name else f"{name}\n({sid})",
-                                                                  
self.controller.get_write_count()))
+        self.label.setToolTip(tr("{}\nSet-VCP writes: {}\nMonitor 
{}").format(sid if id == name else f"{name}\n({sid})",
+                                                                              
self.controller.get_write_count(),
+                                                                              
self.controller.vdu_number))
 
 
 class Preset:
@@ -4247,30 +4362,66 @@
 
 
 class ToolButton(QToolButton):
-
-    def __init__(self, svg_source: bytes, tip: str | None = None, parent: 
QWidget | None = None) -> None:
+    def __init__(self, svg_source: bytes, tip: str | None = None, parent: 
QWidget | None = None):
         super().__init__(parent)
         if tip is not None:
             self.setToolTip(tip)
         self.svg_source = svg_source
+        self._original_icon = None
+        self._busy_timer = QTimer(self)
+        self._busy_timer.timeout.connect(self._update_busy_icon)
+        self._busy_angle = 0
+        self._busy_now = False
         self.refresh_icon()
 
-    def refresh_icon(self, svg_source: bytes | None = None) -> None:  # may 
refresh the theme (coloring light/dark) of the icon
-        if svg_source is not None:  # Either a new icon or if None just a 
light/dark theme refresh
+    def refresh_icon(self, svg_source: bytes | None = None):
+        if svg_source is not None:
             self.svg_source = svg_source
-        self.setIcon(create_icon_from_svg_bytes(self.svg_source))  # this may 
alter the SVG for light/dark theme
+        self._original_icon = create_icon_from_svg_bytes(self.svg_source)   # 
Store the original icon so we can restore it later
+        if not self._busy_now:
+            self.setIcon(self._original_icon)
+
+    def setBusy(self, busy: bool):    # Start or stop the busy spinner 
animation.
+        if busy == self._busy_now:
+            return
+        self._busy_now = busy
+        if busy:   # Start spinning
+            self._busy_angle = 0
+            self._busy_timer.start(30)      # ~33 fps
+        else:   # Stop spinning and restore original icon
+            self._busy_timer.stop()
+            self.setIcon(self._original_icon)
+
+    def _update_busy_icon(self):
+        size = self.iconSize()    # Use the button's icon size (or a default 
size if none)
+        if size.width() <= 0 or size.height() <= 0:
+            size = self.size()              # fallback to button size
+        pixmap = QPixmap(size)
+        pixmap.fill(Qt.GlobalColor.transparent)
+        painter = QPainter(pixmap)
+        painter.setRenderHint(QPainter.RenderHint.Antialiasing)
+        if QT5_QPAINTER_HIGH_QUALITY_ANTIALIASING:
+            painter.setRenderHint(QT5_QPAINTER_HIGH_QUALITY_ANTIALIASING)
+        pen_width = max(npx(2), size.width() // 10)   # Determine a good pen 
width relative to size
+        painter.setPen(QPen(self.palette().buttonText().color(), pen_width, 
Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
+        rect = QRect(0, 0, size.width(), size.height()).adjusted(margin := 
pen_width // npx(2) + npx(1), margin, -margin, -margin)
+        painter.drawArc(rect, self._busy_angle * 16, 270 * 16)     # Draw the 
rotating arc (270 degrees)
+        painter.end()
+        self.setIcon(QIcon(pixmap))
+        self._busy_angle = (self._busy_angle + 8) % 360   # Advance the angle 
for the next frame
 
 
-class VduPanelBottomToolBar(QToolBar):
+class VduMainToolBar(QToolBar):
 
     def __init__(self, tool_buttons: List[ToolButton], app_context_menu: 
ContextMenu, parent: VduControlsMainPanel) -> None:
         super().__init__(parent=parent)
+        self.setObjectName('VduPanelToolBar')  # Internal name for persistence 
- do not change or persistence will be lost.
         self.preset_edit_target: Preset | None = None
+        self.setMovable(False)
         self.tool_buttons = tool_buttons
         for button in self.tool_buttons:
             self.addWidget(button)
         self.setIconSize(QSize(native_font_height(), native_font_height()))
-        self.progress_bar: QProgressBar | None = None
         self.status_area = QStatusBar()
         self.addWidget(self.status_area)
         self.menu_button = ToolButton(MENU_ICON_SOURCE, tr("Context and Preset 
Menu"), self)
@@ -4285,27 +4436,17 @@
 
         self.preset_action.triggered.connect(edit_current_preset)
         self.addWidget(self.menu_button)
-        self.installEventFilter(self)
 
-    def eventFilter(self, target: QObject | None, event: QEvent | None) -> 
bool:
-        # PalletChange happens after the new style sheet is in use.
-        if event and event.type() == QEvent.Type.PaletteChange:
-            for button in self.tool_buttons:
-                button.refresh_icon()
-            self.menu_button.refresh_icon()
-        return super().eventFilter(target, event)
+    def refresh_buttons(self):
+        for button in self.tool_buttons:
+            button.refresh_icon()
+        self.menu_button.refresh_icon()
 
     def indicate_busy(self, is_busy: bool = True) -> None:
-        if is_busy and self.progress_bar is None:
-            self.status_area.clearMessage()
-            self.progress_bar = QProgressBar(self)
-            self.progress_bar.setTextVisible(False)  # Disable text percentage 
label on the spinner progress-bar
-            self.progress_bar.setRange(0, 0)  # 0,0 causes the progress bar to 
pulsate left/right - used as a busy spinner.
-            self.status_area.addWidget(self.progress_bar, 1)
-            self.progress_bar.show()  # According to the Qt docs, this is 
necessary because removing it just hides it.
-        elif self.progress_bar is not None:
-            self.status_area.removeWidget(self.progress_bar)
-            self.progress_bar = None
+        if is_busy:
+            self.tool_buttons[0].setBusy(True)
+        else:
+            self.tool_buttons[0].setBusy(False)
         QApplication.sendPostedEvents(self, 0)  # Flush any change events 
before resetting the flag
         QApplication.processEvents()  # Force the flushed events to be 
processed now
 
@@ -4331,12 +4472,13 @@
 
     def __init__(self) -> None:
         super().__init__()
-        self.bottom_toolbar: VduPanelBottomToolBar | None = None
+        self.main_toolbar: VduMainToolBar | None = None
         self.refresh_data_task = None
         self.setObjectName("vdu_controls_main_panel")
         self.vdu_control_panels: Dict[str, VduControlPanel] = {}
         self.alert: QMessageBox | None = None
         self.main_controller: VduAppController | None = None
+        self.message_history = []
 
     def initialise_control_panels(self, main_controller: VduAppController,
                                   app_context_menu: ContextMenu, main_config: 
VduControlsConfig,
@@ -4354,9 +4496,9 @@
                     old_layout.removeItem(item)
                     item.widget().deleteLater()
         controllers_layout = QVBoxLayout()
-        controllers_layout.setSpacing(0)
+        controllers_layout.setSpacing(npx(5))
         cl_margins = controllers_layout.contentsMargins()
-        controllers_layout.setContentsMargins(cl_margins.left(), 0, 
cl_margins.right(), 0)
+        controllers_layout.setContentsMargins(cl_margins.left(), npx(5), 
cl_margins.right(), npx(5))
         self.setLayout(controllers_layout)
 
         warnings_enabled = main_config.is_set(ConfOpt.WARNINGS_ENABLED)
@@ -4395,8 +4537,8 @@
             no_vdu_layout.addSpacing(32)
             controllers_layout.addWidget(no_vdu_widget)
 
-        self.bottom_toolbar = VduPanelBottomToolBar(tool_buttons=tool_buttons, 
app_context_menu=app_context_menu, parent=self)
-        controllers_layout.addWidget(self.bottom_toolbar, 0, 
Qt.AlignmentFlag.AlignBottom)
+        self.main_toolbar = VduMainToolBar(tool_buttons=tool_buttons, 
app_context_menu=app_context_menu, parent=self)
+        main_controller.replace_toolbar(self.main_toolbar)
 
         def _open_context_menu(position: QPoint) -> None:
             assert app_context_menu is not None
@@ -4406,8 +4548,8 @@
         self.customContextMenuRequested.connect(_open_context_menu)
 
     def indicate_busy(self, is_busy: bool = True, lock_controls: bool = True) 
-> None:
-        if self.bottom_toolbar is not None:
-            self.bottom_toolbar.indicate_busy(is_busy)
+        if self.main_toolbar is not None:
+            self.main_toolbar.indicate_busy(is_busy)
         if lock_controls:
             for control_panel in self.vdu_control_panels.values():
                 control_panel.setDisabled(is_busy)
@@ -4420,8 +4562,8 @@
         return True
 
     def show_active_preset(self, preset: Preset | None) -> None:
-        if self.bottom_toolbar:
-            self.bottom_toolbar.show_active_preset(preset)
+        if self.main_toolbar:
+            self.main_toolbar.show_active_preset(preset)
 
     def show_vdu_exception(self, exception: VduException, can_retry: bool = 
False) -> bool:
         log_error(f"{exception.vdu_description} {exception.operation} 
{exception.attr_id} {exception.cause}")
@@ -4445,7 +4587,16 @@
         return answer == MBtn.Retry
 
     def status_message(self, message: str, timeout: int):
-        self.bottom_toolbar.status_area.showMessage(message, timeout) if 
self.bottom_toolbar else None
+        if message.strip():   # Only non-empty messages, ignore blank 
messages, they're just clearing the status bar.
+            
self.message_history.append(f"\n{datetime.now().strftime("%H:%M:%S")}{MESSAGE_SYMBOL}
 {message}")
+            self.message_history = self.message_history[-9:]
+        if 
self.main_controller.main_config.is_set(ConfOpt.SEPARATE_STATUS_BAR):
+            self.main_controller.main_window.statusBar().showMessage(message, 
timeout)
+            
self.main_controller.main_window.statusBar().setToolTip("".join([tr('Message 
history:')] + self.message_history))
+        elif self.main_toolbar:
+            self.main_toolbar.status_area.showMessage(message, timeout)
+            self.main_toolbar.status_area.setToolTip("".join([tr('Message 
history:')] + self.message_history))
+
 
 @dataclass
 class BulkChangeItem:
@@ -5707,7 +5858,7 @@
         self.vip_menu = QMenu()
         self.vip_menu.triggered.connect(self.vip_menu_triggered)
         edit_panel_layout.addWidget(self.preset_name_edit)
-        self.vdu_init_button = ToolButton(VDU_CONNECTED_ICON_SOURCE, 
tr("Create VDU specific\nInitialization-Preset"), self)
+        self.vdu_init_button = ToolButton(VDU_POWER_ON_ICON_SOURCE, tr("Create 
VDU specific\nInitialization-Preset"), self)
         self.vdu_init_button.setMenu(self.vip_menu)
         
self.vdu_init_button.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
         edit_panel_layout.addWidget(self.vdu_init_button)
@@ -6095,14 +6246,14 @@
          details=tr('Details: 
{}').format(''.join(traceback.format_exception(e_type, e_value, 
e_traceback)))).exec()
 
 
-def create_pixmap_from_svg_bytes(svg_bytes: bytes) -> QPixmap:
+def create_pixmap_from_svg_bytes(svg_bytes: bytes, width: int = 64, height: 
int = 64) -> QPixmap:
     """There is no QIcon option for loading SVG from a string, only from a SVG 
file, so roll our own."""
-    return QPixmap.fromImage(create_image_from_svg_bytes(svg_bytes))
+    return QPixmap.fromImage(create_image_from_svg_bytes(svg_bytes, width, 
height))
 
 
-def create_image_from_svg_bytes(svg_bytes) -> QImage:
+def create_image_from_svg_bytes(svg_bytes, width: int = 64, height: int = 64) 
-> QImage:
     renderer = QSvgRenderer(svg_bytes)
-    image = QImage(64, 64, QImage.Format.Format_ARGB32)
+    image = QImage(width, height, QImage.Format.Format_ARGB32)
     image.fill(0x0)
     painter = QPainter(image)
     renderer.render(painter)
@@ -6552,13 +6703,13 @@
         return round(self.plot_height * percent / 100)
 
     def lux_from_x(self, x) -> int:
-        lux = 0 if x <= 0 else round(10.0 ** (math.log10(1) + (x / 
self.plot_width) * (math.log10(100_000) - math.log10(1))))
+        lux = 0 if x <= 0 else round(10.0 ** ((x / self.plot_width) * 
math.log10(100_000)))
         if lux > 100_000:
             return 100_000
         return lux
 
     def x_from_lux(self, lux: int) -> int:
-        return round((math.log10(lux) - math.log10(1)) / ((math.log10(100000) 
- math.log10(1)) / self.plot_width)) if lux > 0 else 0
+        return round(math.log10(lux) / (math.log10(100000) / self.plot_width)) 
if lux > 0 else 0
 
 @dataclass
 class LuxGaugeHistory:
@@ -6690,9 +6841,10 @@
             painter.drawText(QPointF(middle + npx(6), y + 4), str(i))
         # Draw hour ticks along the bottom
         tick_len = line_width * 2
-        noon_tick_len = line_width * 4
-        for hx in range((hw := 60 // round(minutes_per_point)), hw * 25, hw):
-            painter.drawLine(x := df_plot_left + hx, plot_height, x, 
plot_height - (noon_tick_len if hx == (12 * hw) else tick_len))
+        points_per_hour = df_plot_width / 24
+        for hour in range(24):  # Tick length multiplier: 3 for 0/12, 2 for 
multiples of 3, else 1
+            x = round(df_plot_left + points_per_hour * hour)
+            painter.drawLine(x, plot_height, x, plot_height - tick_len * (3 if 
hour % 12 == 0 else (2 if hour % 3 == 0 else 1)))
         # Draw the sun
         if most_recent_df_xy and most_recent_item and most_recent_df_xy[0]:
             if self.sun_image is None:
@@ -6743,8 +6895,9 @@
         self.updates_enabled = enable
 
     def _y_from_lux(self, lux: int, required_height: int) -> int:
-        return required_height - (
-            round((math.log10(lux) - math.log10(1)) / ((math.log10(200000) - 
math.log10(1)) / required_height)) if lux > 0 else 0)
+        if lux <= 0:
+            return required_height
+        return required_height - round(math.log10(lux) / math.log10(200000) * 
required_height)
 
 
 def lux_create_device(device_name: str) -> LuxMeterDevice:
@@ -6908,7 +7061,7 @@
     def __init__(self) -> None:
         super().__init__(requires_worker=False, manual=True, semi_auto=True)
         self.current_value: float = LuxMeterSemiAutoDevice.get_stored_value()
-        LuxMeterSemiAutoDevice.daylight_factor = None    # Force initilization 
from file
+        LuxMeterSemiAutoDevice.daylight_factor = None    # Force 
initialization from file
         _ = LuxMeterSemiAutoDevice.get_daylight_factor()
 
     def get_value(self) -> float | None:
@@ -7228,7 +7381,7 @@
     def interpolate_brightness(self, smoothed_lux: int, current_point: 
LuxPoint, next_point: LuxPoint) -> int:
 
         def _x_from_lux(lux: int) -> float:
-            return ((math.log10(lux) - math.log10(1)) / (math.log10(100000) - 
math.log10(1))) if lux > 0 else 0
+            return (math.log10(lux) / math.log10(100000)) if lux > 0 else 0
 
         interpolated_brightness = float(current_point.brightness)
         x_smoothed = _x_from_lux(smoothed_lux)
@@ -7252,7 +7405,7 @@
 
     def lux_summary(self, metered_lux: float, smoothed_lux: int) -> str:
         lux_int = round(metered_lux)  # 256 bit char in lux_summary_text can 
cause issues if stdout not utf8 (force utf8 for stdout)
-        return f"{lux_int}{SMOOTHING_SYMBOL}{smoothed_lux} lux" if lux_int != 
smoothed_lux else f"{lux_int} lux"
+        return f"{lux_int} {SMOOTHING_SYMBOL} {smoothed_lux} lux 
{tr('(smoothed)')}" if lux_int != smoothed_lux else f"{lux_int} lux"
 
     def stop(self) -> None:
         super().stop()
@@ -7399,6 +7552,7 @@
         
self.profile_selector_widget.setSizeAdjustPolicy(QListWidget.SizeAdjustPolicy.AdjustToContents)
         self.profile_selector_widget.setFlow(QListWidget.Flow.LeftToRight)
         self.profile_selector_widget.setSpacing(0)
+        self.profile_selector_widget.setMinimumWidth(npx(940))
         
self.profile_selector_widget.setMinimumHeight(native_font_height(scaled=1.4))
 
         main_layout.addWidget(self.profile_selector_widget, stretch=0)
@@ -7963,7 +8117,12 @@
         top_layout.setSpacing(0)
         tl_margins = top_layout.contentsMargins()
         top_layout.setContentsMargins(tl_margins.left(), 0, 
tl_margins.right(), 0)
-        top_layout.addWidget(QLabel(tr("Ambient Light Level (lux)")), 
alignment=Qt.AlignmentFlag.AlignBottom)
+
+        label = IconLabel(AMBIENT_PANEL_ICON_SOURCE, tr("Ambient Light 
Level"), tr("lux"))
+        label.setToolTip(tr("Set the ambient light level to adjust all 
monitors."))
+        top_layout.addWidget(label,
+                             alignment=Qt.AlignmentFlag.AlignBottom)
+
 
         input_panel = QWidget()
         input_panel_layout = QHBoxLayout()
@@ -7976,8 +8135,9 @@
 
         self.slider = ClickableSlider()
         self.slider.setToolTip(tr("Ambient light level input (lux value)"))
+        self.slider.setToolTipDuration(TOOLTIP_DURATION_MSEC)
         self.slider.setMinimumWidth(npx(200))
-        self.slider.setRange(int(math.log10(1) * 1000), int(math.log10(100000) 
* 1000))
+        self.slider.setRange(0, int(math.log10(100000) * 1000))
         self.slider.setSingleStep(1)
         self.slider.setPageStep(100)
         self.slider.setTickInterval(1000)
@@ -7998,6 +8158,7 @@
         self.lux_input_field = QSpinBox()
         self.lux_input_field.setLineEdit(LineEditAll())
         self.lux_input_field.setToolTip(tr("Ambient light level input (lux 
value)"))
+        self.lux_input_field.setToolTipDuration(TOOLTIP_DURATION_MSEC)
         self.lux_input_field.setKeyboardTracking(False)
         self.lux_input_field.setRange(1, 100000)
         self.lux_input_field.setValue(self.current_value)
@@ -8641,7 +8802,7 @@
                             self.status_message(tr("Error during restoration 
preset {}").format(preset.name), timeout=5)
                             return
                         log_info(f"Restored initialization-preset 
'{worker.context.name}'")
-                        message = tr("Restored 
Preset\n{}").format(worker.context.name)
+                        message = tr("Restored I-Preset 
{}").format(worker.context.name)
                         self.status_message(message, timeout=5)
                         self.main_window.splash_message_qtsignal.emit(message)
                         time.sleep(1.0)  # Pause to give the message time to 
display - TODO find non-delaying solution
@@ -8940,6 +9101,15 @@
         self.main_window.app_save_window_state()
         QCoreApplication.exit(EXIT_CODE_FOR_RESTART)
 
+    def replace_toolbar(self, main_toolbar):
+        target_window = self.main_window
+        for old_toolbar in target_window.findChildren(QToolBar):  # Make sure 
there is only one toolbar
+            target_window.removeToolBar(old_toolbar)
+            old_toolbar.deleteLater()
+        at_top = self.main_config.is_set(ConfOpt.TOOLBAR_AT_TOP)
+        toolbar_area = Qt.ToolBarArea.TopToolBarArea if at_top else 
Qt.ToolBarArea.BottomToolBarArea
+        target_window.addToolBar(toolbar_area, main_toolbar)
+
 
 class VduAppWindow(QMainWindow):
     splash_message_qtsignal = pyqtSignal(str)
@@ -8961,6 +9131,8 @@
         self.scroll_area: QScrollArea | None = None
         self.main_config = main_config
         self.hide_shortcuts = True
+        self.initial_theme_is_dark = is_dark_theme()
+        log_info(f"Started with dark theme: {self.initial_theme_is_dark}")
 
         def _run_in_gui(task: Callable):
             log_debug(f"Running task in gui thread {repr(task)}") if 
log_debug_enabled else None
@@ -8968,10 +9140,6 @@
 
         self._run_in_gui_thread_qtsignal.connect(_run_in_gui)
 
-        os_desktop = os.environ.get('XDG_CURRENT_DESKTOP', 
default='unknown').lower()
-        use_gnome_like_tray = main_config.is_set(ConfOpt.SYSTEM_TRAY_ENABLED) 
and (is_gnome_desktop() or is_cosmic_desktop())
-        log_info(f"{os_desktop=} {use_gnome_like_tray=}")  # Gnome tray 
doesn't provide a way to bring up the main app.
-
         global log_debug_enabled
         if log_debug_enabled:
             for screen in app.screens():
@@ -8979,7 +9147,7 @@
 
         self.app_context_menu = ContextMenu(
             app_controller=main_controller,
-            main_window_action=partial(self.show_main_window, True) if 
use_gnome_like_tray else None,
+            main_window_action=partial(self.show_main_window, True),  # Gnome 
tray doesn't provide a way to bring up the main app.
             about_action=partial(AboutDialog.invoke, self.main_controller),
             help_action=HelpDialog.invoke,
             gray_scale_action=GreyScaleDialog,
@@ -9110,17 +9278,16 @@
             self.activateWindow()
 
     def show(self):
-        if self.main_config.is_set(ConfOpt.SMART_WINDOW):
-            if not self.app_restore_window_state():  # No previous state or 
invalid
+        if not self.app_restore_window_state():  # No previous state or invalid
+            if self.main_config.is_set(ConfOpt.SMART_WINDOW):
                 self.adjustSize()
                 self.app_decide_window_position()  # decide initial position 
relative to cursor
                 self.app_save_window_state()
         super().show()
 
     def hide(self):
-        if self.main_config.is_set(ConfOpt.SMART_WINDOW):
-            if self.isVisible():  # Only save position if really on screen
-                self.app_save_window_state()
+        if self.isVisible():  # Only save position if really on screen
+            self.app_save_window_state()
         super().hide()
 
     def quit_app(self) -> None:
@@ -9132,7 +9299,7 @@
         global mono_light_tray
         self.app_icon = QIcon()
         self.app_icon.addPixmap(get_splash_image() if splash_pixmap is None 
else splash_pixmap)
-        tray_theme_type = get_tray_theme_type(self.main_config)
+        tray_theme_type = self.get_tray_theme_type()
         if CUSTOM_TRAY_ICON_FILE.exists() and 
os.access(CUSTOM_TRAY_ICON_FILE.as_posix(), os.R_OK):
             log_info(f"Loading custom app_icon: {CUSTOM_TRAY_ICON_FILE} 
{tray_theme_type=}")
             self.tray_icon = create_icon_from_path(CUSTOM_TRAY_ICON_FILE, 
tray_theme_type)
@@ -9179,7 +9346,6 @@
         self.scroll_area.setWidgetResizable(True)
         self.scroll_area.setWidget(self.main_panel)
         self.setCentralWidget(self.scroll_area)
-
         available_height = self.screen().availableGeometry().height() - 
npx(200)  # Minus allowance for panel/tray
         hint_height = self.main_panel.sizeHint().height()  # The hint is the 
actual required layout space
         hint_width = self.main_panel.sizeHint().width()
@@ -9187,13 +9353,14 @@
         if hint_height > available_height:
             log_debug(f"Main panel too high, adding scroll-area {hint_height=} 
{available_height=}") if log_debug_enabled else None
             self.setMaximumHeight(available_height)
-            self.setMinimumWidth(hint_width + 20)  # Allow extra space for 
disappearing scrollbars
+            self.setMinimumWidth(hint_width + npx(20))  # Allow extra space 
for disappearing scrollbars
         else:  # Don't mess with the size unnecessarily - let the user 
determine it?
-            self.setMinimumHeight(hint_height + 20)
+            number_of_vdus = len(self.main_controller.get_vdu_stable_id_list())
+            self.setMinimumHeight(hint_height + npx(30) * (number_of_vdus + 1))
             if hint_height != self.height():
                 self.setMinimumWidth(self.width())
                 self.adjustSize()
-            self.setMinimumWidth(hint_width + 20)
+            self.setMinimumWidth(hint_width + npx(20))
 
         self.splash_message_qtsignal.emit(tr("Checking Presets"))
 
@@ -9210,6 +9377,19 @@
         PresetsDialog.show_status_message(message=message, timeout=timeout)
         self.status_message(message, timeout=timeout, 
destination=MsgDestination.DEFAULT)
 
+    def get_tray_theme_type(self):   # Ugly because Qt has no way to access 
the tray theme
+        theme = ThemeType.UNTHEMED  # Don't alter colors for overlay onto app 
icon in tray if unthemed
+        if self.main_config.is_set(ConfOpt.MONOCHROME_TRAY_ENABLED):
+            theme = ThemeType.MONOCHROME_DARK
+        if self.main_config.is_set(ConfOpt.MONO_LIGHT_TRAY_ENABLED):
+            theme = ThemeType.MONOCHROME_LIGHT
+        if theme != ThemeType.UNTHEMED:
+            theme_has_flipped = self.initial_theme_is_dark != is_dark_theme()
+            if theme_has_flipped and 
self.main_config.is_set(ConfOpt.TRAY_FOLLOWS_THEME):
+                log_info(f"Option {ConfOpt.TRAY_FOLLOWS_THEME.conf_id} is set: 
Desktop theme flipped - flipping tray theme")
+                theme = ThemeType.MONOCHROME_LIGHT if theme == 
ThemeType.MONOCHROME_DARK else ThemeType.MONOCHROME_DARK
+        return theme
+
     def update_status_indicators(self, preset: Preset|None = None, 
palette_change: bool = False) -> None:
         assert is_running_in_gui_thread()  # Boilerplate in case this is 
called from the wrong thread.
         if self.main_panel is None:  # On deepin 23, events can trigger this 
method before initialization is complete
@@ -9227,7 +9407,7 @@
             self.app_context_menu.indicate_preset_active(preset)
             PresetsDialog.instance_indicate_active_preset(preset)
             title = f"{preset.get_title_name()} {PRESET_APP_SEPARATOR_SYMBOL} 
{title}"
-            tray_embedded_icon = 
preset.create_icon(get_tray_theme_type(self.main_config))
+            tray_embedded_icon = preset.create_icon(self.get_tray_theme_type())
             led1_color = PRESET_TRANSITIONING_LED_COLOR if 
preset.in_transition_step > 0 else None   # TODO transitioning indicator
         if self.main_controller.lux_auto_controller is not None:
             if self.main_controller.lux_auto_controller.is_auto_enabled():
@@ -9237,7 +9417,7 @@
             self.app_context_menu.update_lux_auto_icon(menu_lux_icon)  # Won't 
actually update if it hasn't changed
             if tray_embedded_icon is None and 
self.main_config.is_set(ConfOpt.LUX_TRAY_ICON):
                 if zone := 
self.main_controller.lux_auto_controller.get_lux_zone():
-                    tray_embedded_icon = 
create_icon_from_svg_bytes(zone.icon_svg, get_tray_theme_type(self.main_config))
+                    tray_embedded_icon = 
create_icon_from_svg_bytes(zone.icon_svg, self.get_tray_theme_type())
                     title = title + '\n' + tr("Lighting: 
{}").format(zone.name.lower())
 
         if self.windowTitle() != title:  # Don't change if not needed - 
prevent flickering.
@@ -9276,29 +9456,28 @@
         self.app_save_window_state()
 
     def app_save_window_state(self) -> None:
-        if self.main_config.is_set(ConfOpt.SMART_WINDOW, fallback=True) and 
self.isVisible():
-            log_debug(f"app_save_window_state: {self.pos()=} 
{self.geometry()=} {QtCore.qVersion()}") if log_debug_enabled else None
+        if self.isVisible():
             self.qt_settings.setValue(self.qt_version_key, QtCore.qVersion())
+            log_debug(f"app_save_window_state: {self.pos()=} 
{self.geometry()=} {QtCore.qVersion()}") if log_debug_enabled else None
             self.qt_settings.setValue(self.qt_geometry_key, 
self.saveGeometry())
             self.qt_settings.setValue(self.qt_state_key, self.saveState())
 
     def app_restore_window_state(self) -> bool:
         log_debug(f"app_restore_window_state")
-        if not self.main_config.is_set(ConfOpt.SMART_WINDOW, fallback=True):
-            return False
         if len(self.qt_settings.allKeys()) == 0:  # No previous state
             return False
         save_version_major = self.qt_settings.value(self.qt_version_key, 
'5').split('.', 1)[0]
         qt_version_major = QtCore.qVersion().split('.', 1)[0]
         if save_version_major != qt_version_major:
-            log_warning(f"app_restore_window_state: cannot restore: 
{save_version_major=} != {qt_version_major=}")
-            return False  # Different Qt versions - cannot restore size, 
layout/size/scaling might be different.
-        if geometry := self.qt_settings.value(self.qt_geometry_key, None):
-            self.restoreGeometry(geometry)
+            log_warning(f"app_restore_window_state: restore: 
{save_version_major=} != {qt_version_major=}, this may cause window geometry 
glitches")
+        if smart_window := self.main_config.is_set(ConfOpt.SMART_WINDOW, 
fallback=True):  # Restore pos and geometry
+            if geometry := self.qt_settings.value(self.qt_geometry_key, None):
+                self.restoreGeometry(geometry)
+                log_debug(f"app_restore_window_state: restoring {self.pos()=} 
{self.geometry()=}") if log_debug_enabled else None
         if window_state := self.qt_settings.value(self.qt_state_key, None):
-            self.restoreState(window_state)
-        log_debug(f"app_restore_window_state: {self.pos()=} 
{self.geometry()=}") if log_debug_enabled else None
-        return True
+            self.restoreState(window_state)  # Restore component positions, 
such as toolbar location
+            log_debug(f"app_restore_window_state: restoring internal layout 
state") if log_debug_enabled else None
+        return smart_window
 
 
     def app_decide_window_position(self):
@@ -9330,6 +9509,8 @@
             log_info("PaletteChange event: New style sheet in use, update 
icons")
             self.initialise_app_icon()
             self.update_status_indicators(palette_change=True)
+            if self.main_panel:
+                self.main_panel.main_toolbar.refresh_buttons()
         return super().event(event)
 
     def refresh_preset_menu(self, palette_change: bool = False, reorder: bool 
= False):
@@ -9431,11 +9612,11 @@
 # Coding style also altered for use with vdu_controls.
 def calc_solar_azimuth_zenith(localised_time: datetime, latitude: float, 
longitude: float) -> Tuple[float, float]:
     """Return azimuth degrees clockwise from true north and zenith in degrees 
from vertical direction."""
-
-    utc_date_time = localised_time if localised_time.tzinfo is None else 
localised_time.astimezone(timezone.utc)
+    assert localised_time.tzinfo is not None
+    utc_datetime = localised_time.astimezone(timezone.utc)
     # UTC from now on...
-    hours, minutes, seconds = utc_date_time.hour, utc_date_time.minute, 
utc_date_time.second
-    year, month, day = utc_date_time.year, utc_date_time.month, 
utc_date_time.day
+    hours, minutes, seconds = utc_datetime.hour, utc_datetime.minute, 
utc_datetime.second
+    year, month, day = utc_datetime.year, utc_datetime.month, utc_datetime.day
 
     earth_mean_radius = 6371.01
     astronomical_unit = 149597890
@@ -9493,22 +9674,11 @@
     return azimuth, zenith_angle
 
 
-def true_noon(longitude, when: datetime) -> datetime:
-    b = (360 / 365.25) * (when.timetuple().tm_yday - 81)  # Estimate the 
Equation of Time (in minutes)
-    eot = 9.87 * math.sin(math.radians(2 * b)) - 7.53 * 
math.cos(math.radians(b)) - 1.5 * math.sin(math.radians(b))
-    offset = 4 * longitude + eot   # Calculate the time offset from UTC (in 
minutes)
-    true_noon_utc_minutes = 12 * 60 - offset  # Calculate true noon in UTC 
(12:00 UTC +/- offset)
-    hours = int(true_noon_utc_minutes // 60)
-    minutes = int(true_noon_utc_minutes % 60)
-    when.replace(hour=hours, minute=minutes)
-    return when
-
-
 def calc_solar_lux(localised_time: datetime, location: GeoLocation, 
daylight_factor: float) -> int:
     # E. Elvegård and G. Sjöstedt, "The Calculation of Illumination from Sun 
and Sky," _Illuminating Engineering_, Apr. 1940.
     # [Illuminating Engineering Society, 100 Significant 
Papers](https://www.ies.org/research/publications/100-significant-papers/)
     latitude, longitude = location.latitude, location.longitude
-    azimuth, zenith = calc_solar_azimuth_zenith(true_noon(longitude, 
localised_time), latitude, longitude)
+    _, zenith = calc_solar_azimuth_zenith(localised_time, latitude, longitude)
     solar_altitude = 90 - zenith   # After sunset use
     if solar_altitude < 3:  # 3 degrees is a minimum, the functional limit for 
the algorithm
         return 0
@@ -9519,10 +9689,11 @@
     return illumination
 
 
-# Spherical distance from https://stackoverflow.com/a/21623206/609575
+# Spherical distance from https://stackoverflow.com/a/21623206/609575 (great 
circle distance km)
 def spherical_kilometers(lat1, lon1, lat2, lon2) -> float:
     p = math.pi / 180
     a = 0.5 - math.cos((lat2 - lat1) * p) / 2 + math.cos(lat1 * p) * 
math.cos(lat2 * p) * (1 - math.cos((lon2 - lon1) * p)) / 2
+    a = min(1.0, max(0.0, a))  # Guard against floating‑point errors
     return 12742 * math.asin(math.sqrt(a))
 
 
@@ -9531,7 +9702,7 @@
                                    | None = None) -> Dict[SolarElevationKey, 
SolarElevationData]:
     # Create a minute-by-minute map of today's SolarElevations.
     # For a given dict[SolarElevation], record the first minute it occurs.
-    # Calls the callback for every 1 mimute point, not just each integer 
elevation.
+    # Calls the callback for every 1 minute point, not just each integer 
elevation.
     elevation_time_map = {}
     local_when = local_now.replace(hour=0, minute=0, second=0, microsecond=0)
     while local_when.day == local_now.day:
@@ -9687,64 +9858,5 @@
                  buttons=MBtn.Close).exec()
     sys.exit(rc)
 
-
-# A fallback in case the hard coded splash screen PNG doesn't exist (which 
probably means KDE is not installed).
-# Based on video-display.png from oxygen5-icon-theme-5: LGPL-3.0-only.
-# Convert vdu_controls.png -depth 8 -colors 24 smallest.png; exiftool -all= 
smallest.png; base64 -w 120 smallest.png
-FALLBACK_SPLASH_PNG_BASE64 = b"""
-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
-7ktNAAAAAElFTkSuQmCC
-"""
-
 if __name__ == '__main__':
     main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/vdu_controls-2.4.3/vdu_controls.spec 
new/vdu_controls-2.5.0/vdu_controls.spec
--- old/vdu_controls-2.4.3/vdu_controls.spec    2025-08-29 04:06:52.000000000 
+0200
+++ new/vdu_controls-2.5.0/vdu_controls.spec    2026-04-06 22:33:45.000000000 
+0200
@@ -18,7 +18,7 @@
 
 
 Name:           vdu_controls
-Version:        2.4.3
+Version:        2.5.0
 Release:        0
 Summary:        Visual Display Unit virtual control panel
 License:        GPL-3.0-or-later

Reply via email to