Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hide window from program switcher in NW.JS

I'm writing desktop application with NW.JS (node-webkit). In my application user may open many windows and I would like to hide them from program switcher (alt+tab) and from taskbar. I already found option to hide the window from taksbar, but can't find any way to hide it from program switcher. Is it even possible? Or at least is it possible to group all windows as one (just like in Sticky Notes on Windows)?

like image 692
Felix Black Avatar asked Sep 25 '15 09:09

Felix Black


1 Answers

This feature will probably be present at some point, but as of version 0.12.3 node-webkit doesn't provide any interface for implementing tool type windows, so there's no way to accomplish this with javascript or using the project's package.json file. I can think of two options here.

1. Build node-webkit yourself. The guide is pretty straight forward and comprehensive. You need to modify the constructor of NativeWindowAura in native_aurora_aura.cc, specifically this bit:

#if defined(OS_WIN)
  HWND hwnd = views::HWNDForWidget(window_->GetTopLevelWidget());
  int current_style = ::GetWindowLong(hwnd, GWL_STYLE);
  ::SetWindowLong(hwnd, GWL_STYLE, current_style | WS_CAPTION); //This is the importante line
#endif

to:

  ::SetWindowLong(hwnd, GWL_STYLE, current_style | WS_CAPTION | WS_EX_TOOLWINDOW);

Notice that this is a simple solution that will cause all new windows to default to tool style and not be visible when switching windows. Making this feature available to the manifest file or the window API would require changes on a couple more files.

2. Write and deploy a start application that loads your packaged project (or your project folder), continuously searches for windows with a specific title and sets the window style. Here's an example using a c# console application, but you could use also use c++ or any .NET language for this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Timers;
using System.Diagnostics;

namespace nw
{
   class Program
   {
      const int GWL_EXSTYLE      = -0x14;
      const int WS_EX_APPWINDOW  = 0x40000;
      const int WS_EX_TOOLWINDOW = 0x80;
      const int WS_EX_COMPOSITED = 0x02000000;

      [DllImport("user32", CharSet = CharSet.Auto)]
      static extern int GetWindowLong(IntPtr hwnd, int index);

      [DllImport("User32.Dll")]
      static extern int SetWindowLong(IntPtr hwnd, int index, int newLong);

      [DllImport("user32.dll", CharSet = CharSet.Unicode)]
      static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);

      [DllImport("user32.dll", CharSet = CharSet.Unicode)]
      static extern int GetWindowTextLength(IntPtr hWnd);

      [DllImport("user32.dll")]
      static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);

      [DllImport("user32.dll")]
      static extern IntPtr SetWindowText(IntPtr HWND, string Text);

      delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);


      //------------------------------------------------------------------
      static void Main(string[] args)
      {
         // Test an unpackaged app like:
         // Process.Start(<path\to\nw.exe>, <path\to\project-folder>);

         Process.Start("packaged-nw-app.js");

         Timer timer = new Timer() { Interval = 100, Enabled = true };
         timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

         Console.ReadLine();
      }


      //------------------------------------------------------------------
      static void OnTimedEvent(object source, ElapsedEventArgs e)
      {
         // Find our target windows (change "nw-tool-window" to your window title)
         IEnumerable<IntPtr> windows = FindWindowsWithText("nw-tool-window");

         foreach (var window in windows)
         {
            // Apply WS_EX_TOOLWINDOW to the current style
            SetWindowLong(window, GWL_EXSTYLE, (GetWindowLong(window, GWL_EXSTYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW);
            // Change title to flag as changed
            SetWindowText(window, "nw-tool-window-hidden");
         }
      }


      //------------------------------------------------------------------
      static string GetWindowText(IntPtr hwnd)
      {
         int size = GetWindowTextLength(hwnd);

         if (size > 0)
         {
            var builder = new StringBuilder(size + 1);
            GetWindowText(hwnd, builder, builder.Capacity);
            return builder.ToString();
         }

         return String.Empty;
      }


      //------------------------------------------------------------------
      static IEnumerable<IntPtr> FindWindows(EnumWindowsProc filter)
      {
         IntPtr found = IntPtr.Zero;
         List<IntPtr> windows = new List<IntPtr>();

         EnumWindows(delegate(IntPtr wnd, IntPtr param)
         {
            if (filter(wnd, param))
            {
               windows.Add(wnd);
            }

            return true;
         }, IntPtr.Zero);

         return windows;
      }


      //------------------------------------------------------------
      static IEnumerable<IntPtr> FindWindowsWithText(string titleText)
      {
         return FindWindows(delegate(IntPtr wnd, IntPtr param)
         {
            return GetWindowText(wnd).Contains(titleText);
         });
      } 
   }
}

The code for finding the title was taking from here. If you are only interested in your main window, you can get rid of most of the code above and get away with just using FindWindow and SetWindowLong. You should also probably hide the console or window (if you do this with Forms or WPF), there's enough information on how to accomplish that on SO.

The second approach is a bit of hacky, though, i'd rather go with the first one. And maybe implement it properly instead of hard-coded and open a pull request :)

like image 139
cviejo Avatar answered Nov 13 '22 23:11

cviejo