7
$\begingroup$

Say, I have three opened notebooks Untitled-1, Untitled-2, Untitled-3 in first instance of Mathematica and three notebooks Untitled-1, Untitled-2, Untitled-3 in second instance of Mathematica. How do I distinguish which notebook belong to which instance? Is it possible to have notebooks of one instance to have different window border color then notebooks of another instance? Preferably that it would be done automatically - i.e. each new instance of Mathematica would choose different color of window.

$\endgroup$
12
  • 2
    $\begingroup$ Because usually when one notebook of Mathematica crashes then all other notebooks of the same instance crash too. But notebooks of another instance are unaffected. That is why I always use several instances of Mathematica, but then I am confused, which notebook belong to one instance and which to the other. $\endgroup$ Commented Sep 10, 2021 at 12:11
  • 1
    $\begingroup$ When you open a new notebook, save it with a name that indicates the instance used. $\endgroup$
    – Bob Hanlon
    Commented Sep 10, 2021 at 14:09
  • 2
    $\begingroup$ @Szabolcs Is it this? superuser.com/questions/378995/… $\endgroup$
    – internet
    Commented May 14 at 10:39
  • 2
    $\begingroup$ @internet Yes! I've no idea how that ended up on SuperUser. Probably migrated from SO as "not programming related", one of the many reasons why we couldn't wait to create our own site ... $\endgroup$
    – Szabolcs
    Commented May 14 at 11:01
  • 1
    $\begingroup$ @rhermans One reason I use multiple instances is often I have a notebook running with a calculation taking a long time. That is unusable until the calculation is finished. I open a new notebook with a different kernel and I can continue working with mathematica while the first notebook works away. $\endgroup$
    – Bill Watts
    Commented Jun 15 at 20:23

3 Answers 3

7
+25
$\begingroup$

Based on comments by @Szabolcs, @internet and answer by @BrettChampion.

Inserting the following code into the file $UserBaseDirectory <> "\\Kernel\\init.m":

SetOptions[$FrontEndSession, 
 DockedCells -> 
  Cell[BoxData[ToBoxes["", StandardForm]], "DockedCells", 
   ShowStringCharacters -> False, CellMargins -> {{0, 0}, {0, -15}}, 
   Background -> 
    ColorData[97, Length[SystemProcesses["Mathematica.exe"]]]]]

enter image description here

The width of the cell can be controlled by CellMargins -> {{0, 0}, {0, -15}}.

Still it would be better if the background color of the frame could be changed instead of adding DockedCells. So I still do not consider my question as answered.

There may be some lag of several seconds before the cell appears. I guess because of Length[SystemProcesses["Mathematica.exe"]] which counts number of instances of Mathematica and it seems to be a little bit slow.

$\endgroup$
9
  • $\begingroup$ The solution you want involves communication between frontends, PersistentSymbol may satisfy this need. Besides, you can use First@First@$FrontEnd to distinguish between different frontends. $\endgroup$
    – wioiw
    Commented May 14 at 16:27
  • $\begingroup$ SystemProcesses do the job without need of communication between frontends. $\endgroup$ Commented May 14 at 17:25
  • $\begingroup$ SystemProcesses["Mathematica.exe"] is a bit slow, could use Once[ColorData[97, UnixTime[]]]. $\endgroup$
    – Silvia
    Commented May 17 at 12:45
  • $\begingroup$ @Silvia This does not guarantee that the colors would be distinguishable enough. $\endgroup$ Commented May 17 at 12:58
  • 1
    $\begingroup$ fyi, instead of $UserBaseDirectory <> "Kernel\\init.m" it should be $UserBaseDirectory <> "\\Kernel\\init.m" notice the extra `\` before the kernel. Otherwise it will generate wrong folder name. $\endgroup$
    – Nasser
    Commented May 17 at 18:16
4
$\begingroup$

Your post contains two separate, unrelated questions:

  1. How can I distinguish opened windows by their parent process ID?
  2. How can I change the titlebar/border color of a window?

The following mini package WindowColor solves both problems. However, unfortunately, it solves the 2nd problem only on Windows 11. It looks like you are using Windows 10, which – as far as I know* – does not support per-window settings of colors (you can only change the color of all windows). On Win10, you are therefore stuck only with changing the light/dark theme of a window, so you could only distinguish between two** instances.

Here is an example of using the package. First, obtain a list of all Mathematica windows:

Needs["WindowColor`"];
windows = ListMathematicaWindows[]
(* <|18800 -> {134172, 265248, 857768}, 3588 -> {200468}, 6844 -> {71604}|> *)

Each window is identified with its window handle (hwnd), and they are grouped by their corresponding process ID. As you can see, I have five Mathematica windows, coming from three different Mathematica.exe processes.

Now let's have some fun with the colors! There are four things you can set:

  1. border color (Win11 only)
  2. caption color (Win11 only)
  3. text color (Win11 only)
  4. light/dark mode
window = First@First@windows;
(* These work only on Win11 *)
WindowBorderColor[window, Red];
WindowCaptionColor[window, Darker@Green];
WindowTextColor[window, White];

window = Last@First@windows;
(* This works on Win10 and Win11 *)
WindowDarkMode[window];

enter image description here

Coloring windows by their parent process is now a trivial matter, for example:

colorlist = ColorData[97, "ColorList"];
MapThread[WindowCaptionColor[#1, #2] &, {Values@windows, Take[colorlist, Length[windows]]}];
MapThread[WindowBorderColor[#1, #2] &, {Values@windows, Take[colorlist, Length[windows]]}];
WindowTextColor[Flatten@Values@windows, White];

enter image description here

WindowColor package

This package relies heavily on NetLink and calling Windows DLLs (dwmapi.dll, user32.dll) directly.

BeginPackage["WindowColor`"];
WindowBorderColor::usage="WindowBorderColor[hwnd, color] changes the color of the window border.";
WindowCaptionColor::usage="WindowCaptionColor[hwnd, color] changes the color of the titlebar.";
WindowTextColor::usage="WindowTextColor[hwnd, color] changes the color of the text in the titlebar.";
WindowLightMode::usage="WindowLightMode[hwnd] turns off the dark mode for the window.";
WindowDarkMode::usage="WindowDarkMode[hwnd] turns on the dark mode for the window.";
ListMathematicaWindows::usage="ListMathematicaWindows[] returns an association of all visible windows, created by Mathematica.exe, grouped by their parent process ID.";
Begin["`Private`"];
Needs["NETLink`"];
LoadNETType["System.Diagnostics.Process"];
DwmSetWindowAttribute=DefineDLLFunction["DwmSetWindowAttribute","dwmapi.dll","Int16",{"HWND","DWORD","byte[]","DWORD"}];
EnumWindowsCallbackDelegate = DefineNETDelegate["EnumWindowsCallback", "BOOL", {"HWND", "LPARAM"}];
EnumWindows=DefineDLLFunction["EnumWindows","user32.dll","BOOL",{EnumWindowsCallbackDelegate,"LPARAM"}];
GetWindowThreadProcessId=DefineDLLFunction["GetWindowThreadProcessId","user32.dll","DWORD",{"HWND","out int"}];
IsWindowVisible=DefineDLLFunction["IsWindowVisible","user32.dll","BOOL",{"HWND"}];
(* DWMWINDOWATTRIBUTE enums *)
DWMWAUSEIMMERSIVEDARKMODE=20;
DWMWABORDERCOLOR=34;
DWMWACAPTIONCOLOR=35;
DWMWATEXTCOLOR=36;
(* Auxiliary function for converting numeric hwnd to pointers *)
EnsureHwnd[hwnd_Integer]:=NETNew["System.IntPtr", hwnd];
EnsureHwnd[hwnd_]:=hwnd;
SetAttributes[{WindowBorderColor,WindowCaptionColor,WindowTextColor,WindowDarkMode,WindowLightMode},Listable];
WindowColor[hwnd_,attr_,color_]:=DwmSetWindowAttribute[EnsureHwnd@hwnd,attr,255*Append[List@@RGBColor[color],0],4];
WindowBorderColor[hwnd_,color_]:=WindowColor[hwnd,DWMWABORDERCOLOR,color];
WindowCaptionColor[hwnd_,color_]:=WindowColor[hwnd,DWMWACAPTIONCOLOR,color];
WindowTextColor[hwnd_,color_]:=WindowColor[hwnd,DWMWATEXTCOLOR,color];
WindowDarkMode[hwnd_]:=DwmSetWindowAttribute[EnsureHwnd@hwnd,DWMWAUSEIMMERSIVEDARKMODE,{1},4]
WindowLightMode[hwnd_]:=DwmSetWindowAttribute[EnsureHwnd@hwnd,DWMWAUSEIMMERSIVEDARKMODE,{0},4]
ListMathematicaWindows[]:=Module[{hwnds={},pIDs={},pIDsMathematica={},wins={},enumWindowsCallbackFunction,enumWindowsCallbackDelegate},
(* Get window handles (hwnds) of all visible windows *)
enumWindowsCallbackFunction[hwnd_,lparam_]:=(If[IsWindowVisible[hwnd]===1,AppendTo[hwnds,hwnd];KeepNETObject[hwnd]];1);
enumWindowsCallbackDelegate=NETNewDelegate[EnumWindowsCallbackDelegate,enumWindowsCallbackFunction];
EnumWindows[enumWindowsCallbackDelegate,0];
(* Get corresponding process IDs of all visible windows *)
(GetWindowThreadProcessId[#,pID];AppendTo[pIDs,pID])&/@hwnds;
(* Get process IDs of all Mathematica.exe *)
pIDsMathematica=Sort@Table[p@Id,{p,Process`GetProcessesByName["Mathematica"]}];
(* Select windows created by Mathematica.exe *)
wins = Select[Transpose[{hwnds,pIDs}],MemberQ[pIDsMathematica,Last@#]&];
(* Dereference pointers *)
wins = MapAt[#@ToInt32[]&,wins,{All,1}];
(* Group by process ID *) 
GroupBy[wins,Last->First]
]
End[];
EndPackage[];

* I may be wrong, but please refer to StackOverflow for resolving this. Also, note that applications on Win10 certainly can have customly colored windows, but this is because they use their own functions for drawing windows, not the native Windows ones.
** Obviously, there is another simple solution of using WindowTitle and prepending some distinguishable geometric symbols.

$\endgroup$
2
$\begingroup$

Idea

As explained as my comment under @azerbajdzan's post.

Initialization of global scope:

LocalSymbol@"frontEndSessions" = {};

Initialization of FrontEndSession scope:

AppendTo[LocalSymbol@"frontEndSessions", First@First@$FrontEnd];
SetOptions[$FrontEndSession, DockedCells -> 
    Cell[
        BoxData[ToBoxes["", StandardForm]], 
        "DockedCells", 
        ShowStringCharacters -> False, 
        CellMargins -> {{0, 0}, {0, -15}}, 
        Background -> ColorData[97, Length@LocalSymbol@"frontEndSessions"]
    ]
];
$Epilog := (LocalSymbol@"frontEndSessions" //= DeleteCases[#, First@First@$FrontEnd]&;);

Shortage

$Epilog won't be called when Quit or Exit are not called, such as the situation when you close the last notebook of one FrontEndSession. That means LocalSymbol@"frontEndSessions" may contain dead FrontEndSession.

Performance

3~4 times faster than @azerbajdzan's code, but nearly same if the time of launching Mathematica.exe is taken into account.

$\endgroup$
1
  • $\begingroup$ Maybe NotebookEventActions is a better choice, but needs more detailed design. $\endgroup$
    – wioiw
    Commented Jun 15 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.