diff --git a/etc/cmake/latest/GuiCommands.cmake b/etc/cmake/latest/GuiCommands.cmake
index fa7e929d491345814b045045a03176ffde3babf8..7ec6ae199caa20636ff8345e4fa0638656648da7 100644
--- a/etc/cmake/latest/GuiCommands.cmake
+++ b/etc/cmake/latest/GuiCommands.cmake
@@ -21,57 +21,80 @@ string(REGEX REPLACE "/[^/]+$" "" QT_EXECUTABLE_PRE "${QT_MOC_EXECUTABLE}")
 
 function(armarx_add_qt_plugin TARGET)
     # Parse arguments.
-    set(single_param)
-    set(flag_param)
-    set(multi_param PLUGIN_SOURCES PLUGIN_HEADERS SOURCES HEADERS UI_FILES RESOURCE_FILES
-                    DEPENDENCIES DEPENDENCIES_LEGACY WIDGET_CONTROLLERS)
+    set(single_param WIDGET_CONTROLLER)
+    set(flag_param MANUAL_QT_PLUGIN_SOURCES)
+    set(multi_param SOURCES HEADERS UI_FILES RESOURCE_FILES DEPENDENCIES DEPENDENCIES_LEGACY)
     cmake_parse_arguments(PARSE_ARGV 1 AX "${flag_param}" "${single_param}" "${multi_param}")
     if(DEFINED AX_UNPARSED_ARGUMENTS)
         message(FATAL_ERROR "${TARGET}: Unknown arguments `${AX_UNPARSED_ARGUMENTS}`.")
     endif()
 
-    if(NOT AX_WIDGET_CONTROLLER)
-        message(FATAL_ERROR "${TARGET}: Plugins must have at least one WIDGET_CONTROLLER.")
-    endif()
-
-    # Auto-generate plugin headers and sources if requested.
-    if(${AX_PLUGIN})
-        # GUI plugin name.
-        # Make sure to remove only the last 'GuiPlugin' otherwise you cannot name your plugins '*GuiPlugin'
-        string(REGEX REPLACE "GuiPlugin$" "" ARMARX_GUI_PLUGIN_PREFIX "${ARMARX_TARGET_NAME}")
+    # Variables modified within this scope.
+    set(SOURCES "${AX_SOURCES}")
+    set(HEADERS "${AX_HEADERS}")
+
+    # Auto-generate plugin headers and sources if requested. If not (i.e., if
+    # AX_MANUAL_QT_PLUGIN_SOURCES is set, this function is not different to
+    # `armarx_add_qt_library`, except that the intention of the user is clearer.
+    if(NOT ${AX_MANUAL_QT_PLUGIN_SOURCES})
+        if(NOT AX_WIDGET_CONTROLLER)
+            message(FATAL_ERROR "${TARGET}: Qt plugins must have a registered widget controller in WIDGET_CONTROLLER to generate Qt plugin source files. You can also provide custom sources in SOURCES and HEADERS and enable MANUAL_QT_PLUGIN_SOURCES.")
+        endif()
 
-        # Get relative path for subdir.
-        file(RELATIVE_PATH subdir "${PROJECT_SOURCECODE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+        # Extract controller name from namespace(s).
+        string(REPLACE "::" ";" WIDGET_CONTROLLER_NS_LIST "${AX_WIDGET_CONTROLLER}")
+        list(POP_BACK WIDGET_CONTROLLER_NS_LIST WIDGET_CONTROLLER_NAME)
+        string(REPLACE ";" "::" WIDGET_CONTROLLER_NS "${WIDGET_CONTROLLER_NS_LIST}")
 
         # Get the widget controller header.
-        set(tmp_headers ${HEADERS})
-        set(tmp_regex "(^|\\.*/)${ARMARX_GUI_PLUGIN_PREFIX}WidgetController\\.h")
-        list(FILTER tmp_headers INCLUDE REGEX "${tmp_regex}")
-        list(REMOVE_DUPLICATES tmp_headers)
-        list(LENGTH tmp_headers tmp_headers_len)
-        if(NOT "${tmp_headers_len}" STREQUAL "1")
-            message(FATAL_ERROR "Failed to auto generate the GuiPlugin sources.  Cannot find the widget controller `${tmp_regex}`.")
+        set(HEADERS_CP ${HEADERS})
+        set(HEADERS_FILTER_REGEX "(^|\\.*/)${WIDGET_CONTROLLER_NAME}\\.h")
+        list(FILTER HEADERS_CP INCLUDE REGEX "${HEADERS_FILTER_REGEX}")
+        list(REMOVE_DUPLICATES HEADERS_CP)
+        list(LENGTH HEADERS_CP HEADERS_CP_LEN)
+        if(${HEADERS_CP_LEN} EQUAL 0)
+            message(FATAL_ERROR "${TARGET}: Failed to auto generate the Qt plugin sources.  Cannot find header `${WIDGET_CONTROLLER_NAME}.h` of WIDGET_CONTROLLER `${AX_WIDGET_CONTROLLER}` in supplied HEADERS.")
         endif()
-        list(GET tmp_headers 0 tmp_hdr)
-        set(ARMARX_GUI_PLUGIN_WIDGET_CONTROLLER_HEDER "${subdir}/${tmp_hdr}")
+        list(GET HEADERS_CP 0 WIDGET_CONTROLLER_HEADER_INCLUDE)
+
+        # Get relative path for the include.
+        file(RELATIVE_PATH INCLUDE_PATH "${PROJECT_SOURCECODE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+
+        # Variables for template configuration.
+        set(QT_PLUGIN_NAMESPACE "${WIDGET_CONTROLLER_NS}")
+        set(QT_WIDGET_CONTROLLER_INCLUDE "${INCLUDE_PATH}/${WIDGET_CONTROLLER_HEADER_INCLUDE}")
+        set(QT_WIDGET_CONTROLLER_FQ_NAME "${AX_WIDGET_CONTROLLER}")
+
+        # Generate Qt plugin files.
+        set(OUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/QtPlugin")
+        foreach(EXT h cpp)
+            set(QT_PLUGIN_SOURCE_FILE "${ArmarXGui_TEMPLATES_DIR}/qt_plugin/QtPlugin.in.${EXT}")
 
-        # Generate and add files.
-        set(outfile "${GENERATE_BASE_DIR}/${subdir}/${ARMARX_TARGET_NAME}")
-        foreach(suff h cpp)
-            # TODO: Move template to ArmarXGui
             configure_file(
-                "${ArmarXCore_TEMPLATES_DIR}/GuiPluginTemplate/GuiPlugin.tmp.${suff}"
-                "${outfile}.${suff}"
+                "${QT_PLUGIN_SOURCE_FILE}"
+                "${OUT_FILE}.${EXT}"
+                USE_SOURCE_PERMISSIONS
                 @ONLY
             )
         endforeach()
 
-        list(APPEND SOURCES "${outfile}.cpp")
-        list(APPEND HEADERS "${outfile}.h")
+        # Add generated files to SOURCES and HEADERS.
+        list(APPEND SOURCES "${OUT_FILE}.cpp")
+        list(APPEND HEADERS "${OUT_FILE}.h")
+    else()
+        if(AX_WIDGET_CONTROLLER)
+            message(WARNING "${TARGET}: Not generating sources for WIDGET_CONTROLLER `${AX_WIDGET_CONTROLLERS}` (MANUAL_QT_PLUGIN_SOURCES set, assuming user provided custom sources). You may remove WIDGET_CONTROLLER to get rid of this warning.")
+        endif()
     endif()
 
+    # Define the actual library with auto-generated files appended.
     armarx_add_qt_library(${TARGET}
-
+        SOURCES ${SOURCES}
+        HEADERS ${HEADERS}
+        UI_FILES ${AX_UI_FILES}
+        RESOURCE_FILES ${AX_RESOURCE_FILES}
+        DEPENDENCIES ${AX_DEPENDENCIES}
+        DEPENDENCIES_LEGACY ${AX_DEPENDENCIES_LEGACY}
     )
 endfunction()
 
@@ -80,8 +103,7 @@ function(armarx_add_qt_library TARGET)
     # Parse arguments.
     set(single_param)
     set(flag_param PLUGIN)
-    set(multi_param PLUGIN_SOURCES PLUGIN_HEADERS SOURCES HEADERS UI_FILES RESOURCE_FILES
-                    DEPENDENCIES DEPENDENCIES_LEGACY)
+    set(multi_param SOURCES HEADERS UI_FILES RESOURCE_FILES DEPENDENCIES DEPENDENCIES_LEGACY)
     cmake_parse_arguments(PARSE_ARGV 1 AX "${flag_param}" "${single_param}" "${multi_param}")
     if(DEFINED AX_UNPARSED_ARGUMENTS)
         message(FATAL_ERROR "${TARGET}: Unknown arguments `${AX_UNPARSED_ARGUMENTS}`.")