From b553dff5266deda72e5bc0baf1e90d0990a92711 Mon Sep 17 00:00:00 2001
From: Alexander Golubev <fatzer2@gmail.com>
Date: Fri, 17 Apr 2026 11:34:15 +0300
Subject: twin: allow user's noBorder settings override window's defaults

Allow noBorder settings to override what the window is requesting via
_MOTIF_WM_HINTS if the setting was made by a rule or by the user.

Closes: https://mirror.git.trinitydesktop.org/gitea/TDE/tdebase/issues/731
Signed-off-by: Alexander Golubev <fatzer2@gmail.com>
---
 twin/client.cpp      | 21 ++++++++++++++++++---
 twin/client.h        |  5 ++++-
 twin/manage.cpp      | 10 ++++++----
 twin/rules.cpp       | 15 ++++++++++++++-
 twin/rules.h         | 20 ++++++++++++++++++++
 twin/sm.cpp          |  2 ++
 twin/sm.h            |  1 +
 twin/useractions.cpp |  2 +-
 8 files changed, 66 insertions(+), 10 deletions(-)

diff --git a/twin/client.cpp b/twin/client.cpp
index 50248bc2c..e273b6f02 100644
--- a/twin/client.cpp
+++ b/twin/client.cpp
@@ -167,6 +167,7 @@ Client::Client( Workspace *ws )
     modal = false;
     noborder = false;
     user_noborder = false;
+    user_noborder_forced = false;
     urgency = false;
     ignore_focus_stealing = false;
     demands_attention = false;
@@ -482,7 +483,7 @@ void Client::resizeDecoration( const TQSize& s )
 
 bool Client::noBorder() const
     {
-    return noborder || isFullScreen() || user_noborder || motif_noborder;
+    return noborder || isFullScreen() || user_noborder || ( !user_noborder_forced && motif_noborder );
     }
 
 bool Client::userCanSetNoBorder() const
@@ -496,17 +497,31 @@ bool Client::isUserNoBorder() const
     }
 
 void Client::setUserNoBorder( bool set )
+    {
+    setUserNoBorder(set, true);
+    }
+
+void Client::setUserNoBorder( bool set, bool forced )
     {
     if( !userCanSetNoBorder())
         return;
-    set = rules()->checkNoBorder( set );
-    if( user_noborder == set )
+    auto noBorderApply = rules()->applyNoBorder( set );
+    set = noBorderApply.value;
+    forced |= noBorderApply.wasApplied;
+
+    if( user_noborder == set && user_noborder_forced == forced  )
         return;
     user_noborder = set;
+    user_noborder_forced = forced;
     updateDecoration( true, false );
     updateWindowRules();
     }
 
+bool Client::isUserNoBorderForced() const
+    {
+    return user_noborder_forced;
+    }
+
 bool Client::isModalSystemNotification() const
     {
     unsigned char *data = 0;
diff --git a/twin/client.h b/twin/client.h
index fcca99650..bc29da7d6 100644
--- a/twin/client.h
+++ b/twin/client.h
@@ -150,8 +150,10 @@ class Client : public TQObject, public KDecorationDefines
         int fullScreenMode() const { return fullscreen_mode; } // only for session saving
 
         bool isUserNoBorder() const;
-        void setUserNoBorder( bool set );
+        void setUserNoBorder( bool set ); // BCI: merge with bellow
+        void setUserNoBorder( bool set, bool force /* =true */ );
         bool userCanSetNoBorder() const;
+        bool isUserNoBorderForced() const;
         bool noBorder() const;
 
         bool skipTaskbar( bool from_outside = false ) const;
@@ -553,6 +555,7 @@ class Client : public TQObject, public KDecorationDefines
         uint urgency : 1; // XWMHints, UrgencyHint
         uint ignore_focus_stealing : 1; // don't apply focus stealing prevention to this client
         uint demands_attention : 1;
+        uint user_noborder_forced : 1; //< whether user_noborder should override motif_noborder
         WindowRules client_rules;
         void getWMHints();
         void readIcons();
diff --git a/twin/manage.cpp b/twin/manage.cpp
index 394d7fc20..9926718fa 100644
--- a/twin/manage.cpp
+++ b/twin/manage.cpp
@@ -130,15 +130,17 @@ bool Client::manage( Window w, bool isMapped )
         {
         if ( session->minimized )
             init_minimize = true;
-        if( session->userNoBorder )
-            setUserNoBorder( true );
+        setUserNoBorder( session->userNoBorder , session->userNoBorderForced );
+        }
+    else
+        {
+        setUserNoBorder( false, false );
         }
 
     setShortcut( rules()->checkShortcut( session ? session->shortcut : TQString::null, true ));
 
     init_minimize = rules()->checkMinimize( init_minimize, !isMapped );
-    if( rules()->checkNoBorder( false, !isMapped ))
-        setUserNoBorder( true );
+
     
     checkAndSetInitialRuledOpacity();
 
diff --git a/twin/rules.cpp b/twin/rules.cpp
index a72c4d195..9f8e1bb2a 100644
--- a/twin/rules.cpp
+++ b/twin/rules.cpp
@@ -802,6 +802,19 @@ CHECK_FORCE_RULE( StrictGeometry, bool )
 CHECK_RULE( Shortcut, TQString )
 CHECK_FORCE_RULE( DisableGlobalShortcuts, bool )
 
+RuleApplyResult<bool> WindowRules::applyNoBorder( bool arg, bool init ) const
+    { // NOTE: if you are adding more apply* functions like this turn this into a macro
+    RuleApplyResult<bool> ret{.wasApplied=false, .value = arg};
+    if( rules.count() == 0 )
+        return ret;
+    for(const auto &rule: rules)
+        {
+        if( (ret.wasApplied = rule->applyNoBorder( ret.value, init )) )
+            break;
+        }
+    return ret;
+    }
+
 #undef CHECK_RULE
 #undef CHECK_FORCE_RULE
 
@@ -843,7 +856,7 @@ void Client::applyWindowRules()
     setKeepAbove( keepAbove());
     setKeepBelow( keepBelow());
     setFullScreen( isFullScreen(), true );
-    setUserNoBorder( isUserNoBorder());
+    setUserNoBorder( isUserNoBorder(), isUserNoBorderForced() );
     // FSP
     // AcceptFocus :
     if( workspace()->mostRecentlyActivatedClient() == this
diff --git a/twin/rules.h b/twin/rules.h
index 38e0bf2df..3accc81fa 100644
--- a/twin/rules.h
+++ b/twin/rules.h
@@ -32,6 +32,17 @@ class Rules;
 
 #ifndef KCMRULES // only for twin core
 
+/**
+ * The result of the apply* functions (at the time of writing there is only one: applyNoBorder())
+ * which alongside the value derived from the rules application process return a boolean value
+ * indicating whether any rules were applied.
+ */
+template<typename T>
+struct RuleApplyResult {
+    bool wasApplied; //< true if some rule was applied; false otherwise
+    T value;         //< the actual value returned by the rule application process
+};
+
 class WindowRules
     : public KDecorationDefines
     {
@@ -71,6 +82,15 @@ class WindowRules
         TQString checkShortcut( TQString s, bool init = false ) const;
         bool checkDisableGlobalShortcuts( bool disable ) const;
         bool checkIgnorePosition( bool ignore ) const; // obsolete
+
+        /** @defgroup apply* functions
+         * Like check* functions they are returning result of application of rules but unlike them
+         * alongside the result they are returning a boolean indicating whether any rule was
+         * applied.
+         * @{
+         */
+        RuleApplyResult<bool> applyNoBorder( bool noborder, bool init = false ) const;
+        /** @} */
     private:
         MaximizeMode checkMaximizeVert( MaximizeMode mode, bool init ) const;
         MaximizeMode checkMaximizeHoriz( MaximizeMode mode, bool init ) const;
diff --git a/twin/sm.cpp b/twin/sm.cpp
index 5ef97f62d..138e08697 100644
--- a/twin/sm.cpp
+++ b/twin/sm.cpp
@@ -113,6 +113,7 @@ void Workspace::storeSession( TDEConfig* config, SMSavePhase phase )
             config->writeEntry( TQString("skipTaskbar")+n, c->skipTaskbar( true ) );
             config->writeEntry( TQString("skipPager")+n, c->skipPager() );
             config->writeEntry( TQString("userNoBorder")+n, c->isUserNoBorder() );
+            config->writeEntry( TQString("userNoBorderForced")+n, c->isUserNoBorderForced() );
             config->writeEntry( TQString("windowType")+n, windowTypeToTxt( c->windowType()));
             config->writeEntry( TQString("shortcut")+n, c->shortcut().toStringInternal());
             }
@@ -179,6 +180,7 @@ void Workspace::loadSessionInfo()
         info->skipTaskbar = config->readBoolEntry( TQString("skipTaskbar")+n, false  );
         info->skipPager = config->readBoolEntry( TQString("skipPager")+n, false  );
         info->userNoBorder = config->readBoolEntry( TQString("userNoBorder")+n, false  );
+        info->userNoBorderForced = config->readBoolEntry( TQString("userNoBorderForced")+n, false  );
         info->windowType = txtToWindowType( config->readEntry( TQString("windowType")+n ).latin1());
         info->shortcut = config->readEntry( TQString("shortcut")+n );
         info->active = ( active_client == i );
diff --git a/twin/sm.h b/twin/sm.h
index 951db4e32..04e979842 100644
--- a/twin/sm.h
+++ b/twin/sm.h
@@ -45,6 +45,7 @@ struct SessionInfo
     bool skipTaskbar;
     bool skipPager;
     bool userNoBorder;
+    bool userNoBorderForced;
     NET::WindowType windowType;
     TQString shortcut;
     bool active; // means 'was active in the saved session'
diff --git a/twin/useractions.cpp b/twin/useractions.cpp
index fddc4cb38..ec71b535f 100644
--- a/twin/useractions.cpp
+++ b/twin/useractions.cpp
@@ -450,7 +450,7 @@ void Workspace::performWindowOperation( Client* c, Options::WindowOperation op )
             c->setFullScreen( !c->isFullScreen(), true );
             break;
         case Options::NoBorderOp:
-            c->setUserNoBorder( !c->isUserNoBorder());
+            c->setUserNoBorder( !c->noBorder());
             break;
         case Options::KeepAboveOp:
             {
-- 
cgit v1.2.3

