offapi/UnoApi_offapi.mk                              |    2 
 offapi/org/libreoffice/embindtest/XStringFactory.idl |   18 ++++++
 offapi/org/libreoffice/embindtest/theSingleton.idl   |   16 +++++
 pyuno/PythonTest_pyuno_pytests_embindtest.mk         |    1 
 pyuno/qa/pytests/singleton.py                        |   20 +++++++
 pyuno/source/module/pyuno_except.cxx                 |   36 +++++++++++-
 pyuno/source/module/uno.py                           |   11 +++
 unotest/Library_embindtest.mk                        |    1 
 unotest/source/embindtest/embindtest.component       |    6 ++
 unotest/source/embindtest/singleton.cxx              |   53 +++++++++++++++++++
 10 files changed, 158 insertions(+), 6 deletions(-)

New commits:
commit 8b022d0d0d08e3133f37d6e088b4891deeb1b52c
Author:     Neil Roberts <[email protected]>
AuthorDate: Wed Feb 11 21:57:31 2026 +0100
Commit:     Stephan Bergmann <[email protected]>
CommitDate: Wed Mar 4 13:53:40 2026 +0100

    pyuno: Add a unit test for singletons
    
    As with the test for services, this uses the embindtest framework to
    hide it behind --enable-embindtest-uno.
    
    Change-Id: Ie64848926d21ca5c3edd449de85f4fed16b3b2aa
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199220
    Tested-by: Jenkins
    Reviewed-by: Stephan Bergmann <[email protected]>

diff --git a/offapi/UnoApi_offapi.mk b/offapi/UnoApi_offapi.mk
index 356e97893dcc..3e5d5b1f40e4 100644
--- a/offapi/UnoApi_offapi.mk
+++ b/offapi/UnoApi_offapi.mk
@@ -4399,7 +4399,9 @@ $(eval $(call 
gb_UnoApi_add_idlfiles,offapi,org/libreoffice/embindtest, \
     Template \
     XArgumentStore \
     XAttributes \
+    XStringFactory \
     XTest \
+    theSingleton \
 ))
 $(eval $(call gb_UnoApi_add_idlfiles_nohdl,offapi,org/libreoffice/embindtest, \
     BridgeTest \
diff --git a/offapi/org/libreoffice/embindtest/XStringFactory.idl 
b/offapi/org/libreoffice/embindtest/XStringFactory.idl
new file mode 100644
index 000000000000..9a69b3df72c1
--- /dev/null
+++ b/offapi/org/libreoffice/embindtest/XStringFactory.idl
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+module org { module libreoffice { module embindtest {
+
+interface XStringFactory {
+    string getString();
+};
+
+}; }; };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/offapi/org/libreoffice/embindtest/theSingleton.idl 
b/offapi/org/libreoffice/embindtest/theSingleton.idl
new file mode 100644
index 000000000000..1168f5993350
--- /dev/null
+++ b/offapi/org/libreoffice/embindtest/theSingleton.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+module org { module libreoffice { module embindtest {
+
+singleton theSingleton: XStringFactory;
+
+}; }; };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
diff --git a/pyuno/PythonTest_pyuno_pytests_embindtest.mk 
b/pyuno/PythonTest_pyuno_pytests_embindtest.mk
index 053b05f1d2d1..978e06ecf15e 100644
--- a/pyuno/PythonTest_pyuno_pytests_embindtest.mk
+++ b/pyuno/PythonTest_pyuno_pytests_embindtest.mk
@@ -12,6 +12,7 @@ $(eval $(call 
gb_PythonTest_PythonTest,pyuno_pytests_embindtest))
 $(eval $(call 
gb_PythonTest_add_modules,pyuno_pytests_embindtest,$(SRCDIR)/pyuno/qa/pytests, \
     embindtest \
     serviceconstructors \
+    singleton \
 ))
 
 # vim: set noet sw=4 ts=4:
diff --git a/pyuno/qa/pytests/singleton.py b/pyuno/qa/pytests/singleton.py
new file mode 100644
index 000000000000..bcc9b7cfe26a
--- /dev/null
+++ b/pyuno/qa/pytests/singleton.py
@@ -0,0 +1,20 @@
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+import unittest
+import org.libreoffice.unotest
+from org.libreoffice.embindtest import theSingleton
+
+class SingletonTest(unittest.TestCase):
+    def test_singleton(self):
+        ctx = org.libreoffice.unotest.pyuno.getComponentContext()
+        xStringFactory = theSingleton.get(ctx)
+        self.assertEqual(xStringFactory.getString(), "this is a string from 
XStringFactory")
+
+# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/unotest/Library_embindtest.mk b/unotest/Library_embindtest.mk
index 4c7a92f4d5c1..d9dae0932187 100644
--- a/unotest/Library_embindtest.mk
+++ b/unotest/Library_embindtest.mk
@@ -12,6 +12,7 @@ $(eval $(call gb_Library_Library,embindtest))
 $(eval $(call gb_Library_add_exception_objects,embindtest, \
     unotest/source/embindtest/embindtest \
     unotest/source/embindtest/serviceconstructors \
+    unotest/source/embindtest/singleton \
 ))
 
 $(eval $(call 
gb_Library_set_componentfile,embindtest,unotest/source/embindtest/embindtest,services))
diff --git a/unotest/source/embindtest/embindtest.component 
b/unotest/source/embindtest/embindtest.component
index 30bdc5c3f68f..06d58ed2acb5 100644
--- a/unotest/source/embindtest/embindtest.component
+++ b/unotest/source/embindtest/embindtest.component
@@ -28,4 +28,10 @@
         <service name="org.libreoffice.embindtest.ImplicitConstructor"/>
         <service name="org.libreoffice.embindtest.ExplicitConstructors"/>
     </implementation>
+    <implementation
+            
constructor="org_libreoffice_comp_embindtest_Singleton_get_implementation"
+            name="org.libreoffice.comp.embindtest.theSingleton"
+            single-instance="true">
+        <singleton name="org.libreoffice.embindtest.theSingleton"/>
+    </implementation>
 </component>
diff --git a/unotest/source/embindtest/singleton.cxx 
b/unotest/source/embindtest/singleton.cxx
new file mode 100644
index 000000000000..e89879a67eae
--- /dev/null
+++ b/unotest/source/embindtest/singleton.cxx
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <sal/config.h>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <org/libreoffice/embindtest/XStringFactory.hpp>
+
+namespace com::sun::star::uno
+{
+class XComponentContext;
+}
+
+namespace
+{
+class SingletonTest : public 
cppu::WeakImplHelper<org::libreoffice::embindtest::XStringFactory,
+                                                  css::lang::XServiceInfo>
+{
+public:
+    OUString SAL_CALL getImplementationName() override
+    {
+        return u"org.libreoffice.comp.embindtest.Singleton"_ustr;
+    }
+
+    sal_Bool SAL_CALL supportsService(OUString const& ServiceName) override
+    {
+        return cppu::supportsService(this, ServiceName);
+    }
+
+    css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
+    {
+        return { u"org.libreoffice.embindtest.Singleton"_ustr };
+    }
+
+    OUString SAL_CALL getString() override { return "this is a string from 
XStringFactory"; }
+};
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+org_libreoffice_comp_embindtest_Singleton_get_implementation(
+    css::uno::XComponentContext*, css::uno::Sequence<css::uno::Any> const&)
+{
+    return cppu::acquire(new SingletonTest);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s 
cinkeys+=0=break: */
commit 45d2412c7809db3649ceef1c3c3f538693ed9069
Author:     Neil Roberts <[email protected]>
AuthorDate: Wed Feb 11 16:40:48 2026 +0100
Commit:     Stephan Bergmann <[email protected]>
CommitDate: Wed Mar 4 13:53:28 2026 +0100

    tdf#171122 pyuno: Add class getter methods for singletons
    
    Makes it possible to import the name of a singleton into a Python script
    as a class and then retrieve it with a getter method. Eg, instead of:
    
    desktop = ctx.getValueByName(
        "/singletons/com.sun.star.frame.theDesktop")
    
    you can now do:
    
    from com.sun.star.frame import theDesktop
    desktop = theDesktop.get(ctx)
    
    Change-Id: I009bd2e890cde52aa6273e462621157fc1f63661
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/199219
    Tested-by: Jenkins
    Reviewed-by: Stephan Bergmann <[email protected]>

diff --git a/pyuno/source/module/pyuno_except.cxx 
b/pyuno/source/module/pyuno_except.cxx
index c3d66f84b4d5..1097c2f193a5 100644
--- a/pyuno/source/module/pyuno_except.cxx
+++ b/pyuno/source/module/pyuno_except.cxx
@@ -21,6 +21,7 @@
 #include <typelib/typedescription.hxx>
 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
 #include <com/sun/star/reflection/XServiceTypeDescription2.hpp>
+#include <com/sun/star/reflection/XSingletonTypeDescription.hpp>
 #include <com/sun/star/script/CannotConvertException.hpp>
 
 
@@ -221,6 +222,23 @@ static PyRef createClassForService(
     return ret;
 }
 
+static PyRef createClassForSingleton(
+    const css::uno::Reference<css::reflection::XSingletonTypeDescription>& 
xSingleton,
+    const Runtime& runtime)
+{
+    PyRef ret = createEmptyPyTypeForTypeDescription(xSingleton);
+
+    // Set “get” to be a class method to get the singleton given a single 
context parameter. The
+    // name of the service is stored via a closure.
+    OUString singletonName = xSingleton->getName();
+    PyRef makeGetter = getObjectFromUnoModule(runtime, 
"_uno_make_singleton_getter");
+    PyRef getter = PyObject_CallOneArg(makeGetter.get(), 
ustring2PyString(singletonName).get());
+
+    PyObject_SetAttrString(ret.get(), "get", getter.get());
+
+    return ret;
+}
+
 /// @throws RuntimeException
 static PyRef createClass( const OUString & name, const Runtime &runtime )
 {
@@ -230,8 +248,8 @@ static PyRef createClass( const OUString & name, const 
Runtime &runtime )
     if (desc.is())
         return createClassFromTypeDescription(name, desc.get(), runtime);
 
-    // If there’s no type description from the typelib then check if it’s a 
service using the type
-    // description manager.
+    // If there’s no type description from the typelib then check if it’s a 
service or a singleton
+    // using the type description manager.
     css::uno::Any xType;
 
     try
@@ -243,10 +261,18 @@ static PyRef createClass( const OUString & name, const 
Runtime &runtime )
         // This will flow through to throw a runtime exception below
     }
 
-    css::uno::Reference<css::reflection::XServiceTypeDescription2> xService;
+    if (xType.hasValue())
+    {
+        css::uno::Reference<css::reflection::XServiceTypeDescription2> 
xService;
+
+        if ((xType >>= xService) && xService.is())
+            return createClassForService(xService);
 
-    if ((xType >>= xService) && xService.is())
-        return createClassForService(xService);
+        css::uno::Reference<css::reflection::XSingletonTypeDescription> 
xSingleton;
+
+        if ((xType >>= xSingleton) && xSingleton.is())
+            return createClassForSingleton(xSingleton, runtime);
+    }
 
     throw RuntimeException("pyuno.getClass: uno exception " + name + " is 
unknown");
 }
diff --git a/pyuno/source/module/uno.py b/pyuno/source/module/uno.py
index 5cb6e6de9385..698fef9590b5 100644
--- a/pyuno/source/module/uno.py
+++ b/pyuno/source/module/uno.py
@@ -384,7 +384,7 @@ def _uno_import(name, *optargs, **kwargs):
             failed = False
 
             try:
-                # check for structs, exceptions, interfaces or services
+                # check for structs, exceptions, interfaces, services or 
singletons
                 d[class_name] = pyuno.getClass(name + "." + class_name)
             except RuntimeException:
                 # check for enums
@@ -557,4 +557,13 @@ def _uno_extract_printable_stacktrace(trace):
 
     return ''.join(traceback.format_tb(trace))
 
+def _uno_make_singleton_getter(singleton_name):
+    value_name = "/singletons/" + singleton_name
+
+    @classmethod
+    def get(_cls, ctx):
+        return ctx.getValueByName(value_name)
+
+    return get
+
 # vim: set shiftwidth=4 softtabstop=4 expandtab:

Reply via email to