3
// Copyright (C) 2008 GNOME Do
5
// This program is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
// GNU General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program. If not, see <http://www.gnu.org/licenses/>.
20
using System.Collections.Generic;
21
using System.Diagnostics;
24
using System.Text.RegularExpressions;
30
namespace Do.Interface.Wink
33
public static class WindowUtils
35
enum OpenOfficeProducts {
43
static bool initialized;
45
static string RemapFile {
46
get { return Path.Combine (Services.Paths.UserDataDirectory, "RemapFile"); }
49
static IEnumerable<string> PrefixStrings {
58
yield return "python(\\d.\\d)?";
59
yield return "(ba)?sh";
63
public static IEnumerable<Regex> BadPrefixes { get; private set; }
65
static Dictionary<string, string> RemapDictionary { get; set; }
67
static List<Window> window_list;
68
static bool window_list_update_needed;
70
static Dictionary<int, string> exec_lines = new Dictionary<int, string> ();
71
static DateTime last_update = new DateTime (0);
74
public static void Initialize ()
80
Wnck.Global.ClientType = Wnck.ClientType.Pager;
82
List<Regex> regex = new List<Regex> ();
83
foreach (string s in PrefixStrings) {
84
regex.Add (new Regex (string.Format ("^{0}$", s), RegexOptions.IgnoreCase));
87
BadPrefixes = regex.AsEnumerable ();
89
Wnck.Screen.Default.WindowClosed += delegate {
90
window_list_update_needed = true;
93
Wnck.Screen.Default.WindowOpened += delegate {
94
window_list_update_needed = true;
97
Wnck.Screen.Default.ApplicationOpened += delegate {
98
window_list_update_needed = true;
101
Wnck.Screen.Default.ApplicationClosed += delegate {
102
window_list_update_needed = true;
105
BuildRemapDictionary ();
109
#region Private Methods
110
static void BuildRemapDictionary ()
112
if (!File.Exists (RemapFile)) {
113
RemapDictionary = BuildDefaultRemapDictionary ();
116
using (StreamWriter writer = new StreamWriter (RemapFile)) {
117
writer.WriteLine ("# Docky Remap File");
118
writer.WriteLine ("# Add key value pairs following dictionary syntax");
119
writer.WriteLine ("# key, value");
120
writer.WriteLine ("# key, altKey, value");
121
writer.WriteLine ("# Lines starting with # are comments, otherwise # is a valid character");
123
foreach (KeyValuePair<string, string> kvp in RemapDictionary) {
124
writer.WriteLine ("{0}, {1}", kvp.Key, kvp.Value);
131
RemapDictionary = new Dictionary<string, string> ();
134
using (StreamReader reader = new StreamReader (RemapFile)) {
136
while (!reader.EndOfStream) {
137
line = reader.ReadLine ();
138
if (line.StartsWith ("#") || !line.Contains (","))
140
string [] array = line.Split (',');
141
if (array.Length < 2 || array [0].Length == 0)
144
string val = array [array.Length - 1].Trim ().ToLower ();
145
if (string.IsNullOrEmpty (val))
148
for (int i=0; i < array.Length - 1; i++) {
149
string key = array [i].Trim ().ToLower ();
150
if (string.IsNullOrEmpty (key))
152
RemapDictionary [key] = val;
159
Log.Error ("Could not read remap file");
160
RemapDictionary = BuildDefaultRemapDictionary ();
165
static Dictionary<string, string> BuildDefaultRemapDictionary ()
167
Dictionary<string, string> remapDict = new Dictionary<string, string> ();
168
remapDict ["banshee.exe"] = "banshee";
169
remapDict ["banshee-1"] = "banshee";
170
remapDict ["azureus"] = "vuze";
171
remapDict ["thunderbird-3.0"] = "thunderbird";
172
remapDict ["thunderbird-bin"] = "thunderbird";
177
static void UpdateExecList ()
179
if ((DateTime.UtcNow - last_update).TotalMilliseconds < 200) return;
181
Dictionary<int, string> old = exec_lines;
183
exec_lines = new Dictionary<int, string> ();
185
foreach (string dir in Directory.GetDirectories ("/proc")) {
187
try { pid = Convert.ToInt32 (Path.GetFileName (dir)); }
190
if (old.ContainsKey (pid)) {
191
exec_lines [pid] = old [pid];
195
string exec_line = CmdLineForPid (pid);
196
if (string.IsNullOrEmpty (exec_line))
199
if (exec_line.Contains ("java") && exec_line.Contains ("jar")) {
200
foreach (Window window in GetWindows ()) {
204
if (window.Pid == pid || window.Application.Pid == pid) {
205
exec_line = window.ClassGroup.ResClass;
208
if (exec_line == "SWT")
209
exec_line = window.Name;
214
exec_line = ProcessExecString (exec_line);
216
exec_lines [pid] = exec_line;
219
last_update = DateTime.UtcNow;
222
static ClickAction GetClickAction (IEnumerable<Window> windows)
225
return ClickAction.None;
227
if (windows.Any (w => w.IsMinimized && w.IsInViewport (Wnck.Screen.Default.ActiveWorkspace)))
228
return ClickAction.Restore;
230
if (windows.Any (w => w.IsActive && w.IsInViewport (Wnck.Screen.Default.ActiveWorkspace)))
231
return ClickAction.Minimize;
233
return ClickAction.Focus;
237
#region Public Methods
239
/// Returns a list of all windows on the default screen
242
/// A <see cref="List"/>
244
public static List<Window> GetWindows ()
246
if (window_list == null || window_list_update_needed)
247
window_list = new List<Window> (Wnck.Screen.Default.WindowsStacked);
253
/// Gets the command line excec string for a PID
255
/// <param name="pid">
256
/// A <see cref="System.Int32"/>
259
/// A <see cref="System.String"/>
261
public static string CmdLineForPid (int pid)
263
string cmdline = null;
266
string procPath = new [] { "/proc", pid.ToString (), "cmdline" }.Aggregate (Path.Combine);
267
using (StreamReader reader = new StreamReader (procPath)) {
268
cmdline = reader.ReadLine ();
276
public static List<Window> WindowListForCmd (string exec)
278
List<Window> windows = new List<Window> ();
279
if (string.IsNullOrEmpty (exec))
283
if (exec.Contains ("ooffice")) {
284
return GetOpenOfficeWindows (exec);
287
exec = ProcessExecString (exec);
288
if (string.IsNullOrEmpty (exec))
293
foreach (KeyValuePair<int, string> kvp in exec_lines) {
294
if (!string.IsNullOrEmpty (kvp.Value) && kvp.Value.Contains (exec)) {
295
// we have a matching exec, now we just find every window whose PID matches this exec
296
foreach (Window window in GetWindows ()) {
300
// this window matches the right PID and exec string, we can match it.
301
bool pidMatch = window.Pid == kvp.Key ||
302
(window.Application != null && window.Application.Pid == kvp.Key);
305
windows.Add (window);
310
return windows.Distinct ().ToList ();
313
static List<Window> GetOpenOfficeWindows (string exec)
315
if (exec.Contains ("writer")) {
316
return GetWindows ().Where ((Wnck.Window w) => w.Name.Contains ("OpenOffice.org Writer")).ToList ();
317
} else if (exec.Contains ("math")) {
318
return GetWindows ().Where ((Wnck.Window w) => w.Name.Contains ("OpenOffice.org Math")).ToList ();
319
} else if (exec.Contains ("calc")) {
320
return GetWindows ().Where ((Wnck.Window w) => w.Name.Contains ("OpenOffice.org Calc")).ToList ();
321
} else if (exec.Contains ("impress")) {
322
return GetWindows ().Where ((Wnck.Window w) => w.Name.Contains ("OpenOffice.org Impress")).ToList ();
323
} else if (exec.Contains ("draw")) {
324
return GetWindows ().Where ((Wnck.Window w) => w.Name.Contains ("OpenOffice.org Draw")).ToList ();
326
return new List<Window> (0);
331
/// This method takes in an "execution string" from proc and applies a heureustic to try
332
/// to magic out the name of the actual executing application. The executing binary is not
333
/// the desired target all the time.
335
/// <param name="exec">
336
/// A <see cref="System.String"/>
339
/// A <see cref="System.String"/>
341
public static string ProcessExecString (string exec)
343
if (string.IsNullOrEmpty (exec))
346
// lower it and trim off white space so we can abuse whitespace a bit
347
exec = exec.ToLower ().Trim ();
349
// if the user has specified a specific mapping, we can use that here
350
if (RemapDictionary.ContainsKey (exec))
351
return RemapDictionary [exec];
353
// this is the "split" character or the argument separator. If the string contains a null
354
// it was fetched from /proc/PID/cmdline and will be nicely split up. Otherwise things get a bit
355
// nasty, and it likely came from a .desktop file.
356
char splitChar = Convert.ToChar (0x0);
357
splitChar = exec.Contains (splitChar) ? splitChar : ' ';
359
// this part is here soley for the remap file so that users may specify to remap based on just the name
360
// without the full path. If no remap file match is found, the net effect of this is nothing.
361
if (exec.StartsWith ("/")) {
362
string first_part = exec.Split (splitChar) [0];
363
int length = first_part.Length;
364
first_part = first_part.Split ('/').Last ();
366
if (length < exec.Length)
367
first_part = first_part + " " + exec.Substring (length + 1);
369
if (RemapDictionary.ContainsKey (first_part)) {
370
return RemapDictionary [first_part];
374
string [] parts = exec.Split (splitChar);
375
for (int i = 0; i < parts.Length; i++) {
376
// we're going to use this a lot
377
string out_val = parts [i];
379
// arguments are useless
380
if (out_val.StartsWith ("-"))
383
// we want the end of paths
384
if (out_val.Contains ("/"))
385
out_val = out_val.Split ('/').Last ();
387
// wine apps can do it backwards... who knew?
388
if (out_val.Contains ("\\"))
389
out_val = out_val.Split ('\\').Last ();
391
// null out our part if is a bad prefix
392
foreach (Regex regex in BadPrefixes) {
393
if (regex.IsMatch (out_val)) {
399
// check if it was a bad prefix...
400
if (!string.IsNullOrEmpty (out_val)) {
401
// sometimes we hide things with shell scripts. This is the most common method of doing it.
402
if (out_val.EndsWith (".real"))
403
out_val = out_val.Substring (0, out_val.Length - ".real".Length);
405
// give the remap dictionary one last shot at this
406
if (RemapDictionary.ContainsKey (out_val))
407
out_val = RemapDictionary [out_val];
415
/// Performs the "logical" click action on an entire group of applications
417
/// <param name="apps">
418
/// A <see cref="IEnumerable"/>
420
public static void PerformLogicalClick (IEnumerable<Window> windows)
422
List<Window> stack = new List<Window> (Wnck.Screen.Default.WindowsStacked);
423
windows = windows.OrderByDescending (w => stack.IndexOf (w));
425
bool not_in_viewport = !windows.Any (w => !w.IsSkipTasklist && w.IsInViewport (w.Screen.ActiveWorkspace));
426
bool urgent = windows.Any (w => w.NeedsAttention ());
428
if (not_in_viewport || urgent) {
429
foreach (Wnck.Window window in windows) {
430
if (urgent && !window.NeedsAttention ())
432
if (!window.IsSkipTasklist) {
433
WindowControl.IntelligentFocusOffViewportWindow (window, windows);
439
switch (GetClickAction (windows)) {
440
case ClickAction.Focus:
441
WindowControl.FocusWindows (windows);
443
case ClickAction.Minimize:
444
WindowControl.MinimizeWindows (windows);
446
case ClickAction.Restore:
447
WindowControl.RestoreWindows (windows);