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
using Docky.Interface;
34
namespace Docky.Utilities
38
public static class WindowUtils
40
static string RemapFile {
41
get { return Path.Combine (Services.Paths.UserDataDirectory, "RemapFile"); }
44
public static IEnumerable<string> BadPrefixes {
53
yield return "python(\\d.\\d)?";
57
static Dictionary<string, string> RemapDictionary { get; set; }
59
static List<Application> application_list;
60
static bool application_list_update_needed;
62
static Dictionary<int, string> exec_lines = new Dictionary<int, string> ();
63
static DateTime last_update = new DateTime (0);
67
Wnck.Screen.Default.WindowClosed += delegate {
68
application_list_update_needed = true;
71
Wnck.Screen.Default.WindowOpened += delegate {
72
application_list_update_needed = true;
75
Wnck.Screen.Default.ApplicationOpened += delegate {
76
application_list_update_needed = true;
79
Wnck.Screen.Default.ApplicationClosed += delegate {
80
application_list_update_needed = true;
83
BuildRemapDictionary ();
86
static void BuildRemapDictionary ()
88
if (!File.Exists (RemapFile)) {
89
RemapDictionary = BuildDefaultRemapDictionary ();
91
StreamWriter writer = null;
93
writer = new StreamWriter (RemapFile);
94
writer.WriteLine ("# Docky Remap File");
95
writer.WriteLine ("# Add key value pairs following dictionary syntax");
96
writer.WriteLine ("# key, value");
97
writer.WriteLine ("# key, altKey, value");
98
writer.WriteLine ("# Lines starting with # are comments, otherwise # is a valid character");
100
foreach (KeyValuePair<string, string> kvp in RemapDictionary) {
101
writer.WriteLine ("{0}, {1}", kvp.Key, kvp.Value);
109
RemapDictionary = new Dictionary<string, string> ();
111
StreamReader reader = null;
113
reader = new StreamReader (RemapFile);
116
while (!reader.EndOfStream) {
117
line = reader.ReadLine ();
118
if (line.StartsWith ("#") || !line.Contains (","))
120
string [] array = line.Split (',');
121
if (array.Length < 2 || array [0].Length == 0)
124
string val = array [array.Length - 1].Trim ().ToLower ();
125
if (string.IsNullOrEmpty (val))
128
for (int i=0; i < array.Length - 1; i++) {
129
string key = array [i].Trim ().ToLower ();
130
if (string.IsNullOrEmpty (key))
132
RemapDictionary [key] = val;
138
Log.Error ("Could not read remap file");
139
RemapDictionary = BuildDefaultRemapDictionary ();
147
static Dictionary<string, string> BuildDefaultRemapDictionary ()
149
Dictionary<string, string> remapDict = new Dictionary<string, string> ();
150
remapDict ["banshee.exe"] = "banshee";
151
remapDict ["banshee-1"] = "banshee";
152
remapDict ["azureus"] = "vuze";
153
remapDict ["thunderbird-3.0"] = "thunderbird";
154
remapDict ["thunderbird-bin"] = "thunderbird";
160
/// Returns a list of all applications on the default screen
163
/// A <see cref="Application"/> array
165
public static List<Application> GetApplications ()
167
if (application_list == null || application_list_update_needed) {
168
application_list = new List<Application> ();
169
foreach (Window w in Wnck.Screen.Default.Windows) {
170
if (!application_list.Contains (w.Application))
171
application_list.Add (w.Application);
174
return application_list;
178
/// Gets the command line excec string for a PID
180
/// <param name="pid">
181
/// A <see cref="System.Int32"/>
184
/// A <see cref="System.String"/>
186
public static string CmdLineForPid (int pid)
189
string cmdline = null;
192
string procPath = new [] { "/proc", pid.ToString (), "cmdline" }.Aggregate (Path.Combine);
193
reader = new StreamReader (procPath);
194
cmdline = reader.ReadLine ().Replace (Convert.ToChar (0x0), ' ');
203
/// Returns a list of applications that match an exec string
205
/// <param name="exec">
206
/// A <see cref="System.String"/>
209
/// A <see cref="List"/>
211
public static List<Application> GetApplicationList (string exec)
213
List<Application> apps = new List<Application> ();
214
if (string.IsNullOrEmpty (exec))
217
exec = ProcessExecString (exec);
218
if (string.IsNullOrEmpty (exec))
223
foreach (KeyValuePair<int, string> kvp in exec_lines) {
224
if (kvp.Value != null && kvp.Value.Contains (exec)) {
225
foreach (Application app in GetApplications ()) {
229
if (app.Pid == kvp.Key || app.Windows.Any (w => w.Pid == kvp.Key)) {
230
if (app.Windows.Any (win => !win.IsSkipTasklist))
240
static void UpdateExecList ()
242
if ((DateTime.UtcNow - last_update).TotalMilliseconds < 200) return;
246
foreach (string dir in Directory.GetDirectories ("/proc")) {
248
try { pid = Convert.ToInt32 (Path.GetFileName (dir)); }
251
string exec_line = CmdLineForPid (pid);
252
if (string.IsNullOrEmpty (exec_line))
255
if (exec_line.Contains ("java") && exec_line.Contains ("jar")) {
256
foreach (Application app in GetApplications ()) {
260
if (app.Pid == pid || app.Windows.Any (w => w.Pid == pid)) {
261
foreach (Wnck.Window window in app.Windows.Where (win => !win.IsSkipTasklist)) {
262
exec_line = window.ClassGroup.ResClass;
265
if (exec_line == "SWT")
266
exec_line = window.Name;
267
Console.WriteLine (exec_line);
274
exec_line = ProcessExecString (exec_line);
276
exec_lines [pid] = exec_line;
279
last_update = DateTime.UtcNow;
282
public static string ProcessExecString (string exec)
284
exec = exec.ToLower ().Trim ();
286
if (RemapDictionary.ContainsKey (exec))
287
return RemapDictionary [exec];
289
if (exec.StartsWith ("/")) {
290
string first_part = exec.Split (' ') [0];
291
int length = first_part.Length;
292
first_part = first_part.Split ('/').Last ();
294
if (length < exec.Length)
295
first_part = first_part + " " + exec.Substring (length + 1);
297
if (RemapDictionary.ContainsKey (first_part)) {
298
return RemapDictionary [first_part];
302
string [] parts = exec.Split (' ');
303
for (int i = 0; i < parts.Length; i++) {
304
if (parts [i].StartsWith ("-"))
307
if (parts [i].Contains ("/"))
308
parts [i] = parts [i].Split ('/').Last ();
311
foreach (string prefix in BadPrefixes) {
312
regex = new Regex (string.Format ("^{0}$", prefix), RegexOptions.IgnoreCase);
313
if (regex.IsMatch (parts [i])) {
319
if (!string.IsNullOrEmpty (parts [i])) {
320
string out_val = parts [i];
321
if (RemapDictionary.ContainsKey (out_val))
322
out_val = RemapDictionary [out_val];
330
/// Performs the "logical" click action on an entire group of applications
332
/// <param name="apps">
333
/// A <see cref="IEnumerable"/>
335
public static void PerformLogicalClick (IEnumerable<Application> apps)
337
List<Window> stack = new List<Window> (Wnck.Screen.Default.WindowsStacked);
338
IEnumerable<Window> windows = apps
339
.SelectMany (app => app.Windows)
340
.OrderByDescending (w => stack.IndexOf (w));
342
bool not_in_viewport = !windows.Any (w => !w.IsSkipTasklist && w.IsInViewport (w.Screen.ActiveWorkspace));
343
bool urgent = windows.Any (w => w.NeedsAttention ());
345
if (not_in_viewport || urgent) {
346
foreach (Wnck.Window window in windows) {
347
if (urgent && !window.NeedsAttention ())
349
if (!window.IsSkipTasklist) {
350
WindowControl.IntelligentFocusOffViewportWindow (window, windows);
356
switch (GetClickAction (apps)) {
357
case ClickAction.Focus:
358
WindowControl.FocusWindows (windows);
360
case ClickAction.Minimize:
361
WindowControl.MinimizeWindows (windows);
363
case ClickAction.Restore:
364
WindowControl.RestoreWindows (windows);
369
static ClickAction GetClickAction (IEnumerable<Application> apps)
372
return ClickAction.None;
374
foreach (Wnck.Application app in apps) {
375
foreach (Wnck.Window window in app.Windows) {
376
if (window.IsMinimized && window.IsInViewport (Wnck.Screen.Default.ActiveWorkspace))
377
return ClickAction.Restore;
381
foreach (Wnck.Application app in apps) {
382
foreach (Wnck.Window window in app.Windows) {
383
if (window.IsActive && window.IsInViewport (Wnck.Screen.Default.ActiveWorkspace))
384
return ClickAction.Minimize;
388
return ClickAction.Focus;