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.Collections.ObjectModel;
24
using System.Runtime.Serialization;
25
using System.Runtime.Serialization.Formatters.Binary;
26
using System.Security;
27
using System.Security.Permissions;
31
using Do.Interface.Wink;
35
using Docky.Interface;
36
using Docky.Utilities;
40
namespace Docky.Core.Default
42
public class ItemsService : IItemsService
44
const int UpdateDelay = 20;
46
// stores items tied with their original identifier so we can remove them later
47
Dictionary<string, AbstractDockItem> custom_items;
49
// a collection for each of the major types of item
50
List<AbstractDockItem> output_items, task_items, stat_items;
52
// this will be a readonly collection to track out output items
53
ReadOnlyCollection<AbstractDockItem> readonly_output_items;
55
bool CustomItemsRead { get; set; }
57
IEnumerable<AbstractDockItem> Docklets {
58
get { return DockServices.DockletService.ActiveDocklets.Cast<AbstractDockItem> (); }
62
/// Our main menu. We only ever need one so we will store it here
64
AbstractDockItem MenuItem { get; set; }
67
/// Our separator. we can re-use this to render over and over if need be.
69
AbstractDockItem Separator { get; set; }
72
/// The path to the file we will use to serialize out our custom items
74
string CustomItemsPath {
76
return Path.Combine (Services.Paths.UserDataDirectory, GetType ().Name + "_CustomItems");
81
/// The path to the file we will use to store our sort dictionary
83
string SortDictionaryPath {
85
return Path.Combine (Services.Paths.UserDataDirectory, GetType ().Name + "_SortDictionary");
89
string DesktopFilesDirectory {
91
return Path.Combine (Services.Paths.UserDataDirectory, GetType ().Name + "_DesktopFiles");
95
IEnumerable<AbstractDockItem> OrderedItems {
98
.Concat (custom_items.Values)
100
.OrderBy (di => di.Position);
106
if (!OrderedItems.Any ())
108
return OrderedItems.Max ((Func<AbstractDockItem, int>) (di => di.Position));
114
public ItemsService ()
116
// build our data structures
117
custom_items = new Dictionary<string, AbstractDockItem> ();
118
task_items = new List<AbstractDockItem> ();
119
output_items = new List<AbstractDockItem> ();
120
stat_items = new List<AbstractDockItem> ();
122
// hook up our read only collection
123
readonly_output_items = output_items.AsReadOnly ();
125
Separator = new SeparatorItem ();
126
MenuItem = new DoDockItem ();
128
if (!Directory.Exists (DesktopFilesDirectory))
129
Directory.CreateDirectory (DesktopFilesDirectory);
136
#region Event Handling
137
void RegisterEvents ()
140
Services.Core.UniverseInitialized += HandleUniverseInitialized;
143
Wnck.Screen.Default.WindowClosed += HandleWindowClosed;
144
Wnck.Screen.Default.WindowOpened += HandleWindowOpened;
147
DockPreferences.AutomaticIconsChanged += HandleAutomaticIconsChanged;
148
DockServices.DockletService.AppletVisibilityChanged += HandleAppletVisibilityChanged;
153
void RegisterDocklets ()
155
RegisterDockItem (MenuItem);
157
foreach (AbstractDockItem item in DockServices.DockletService.Docklets) {
158
RegisterDockItem (item);
162
void UnregisterEvents ()
165
Services.Core.UniverseInitialized -= HandleUniverseInitialized;
168
Wnck.Screen.Default.WindowClosed -= HandleWindowClosed;
169
Wnck.Screen.Default.WindowOpened -= HandleWindowOpened;
172
DockPreferences.AutomaticIconsChanged -= HandleAutomaticIconsChanged;
173
DockServices.DockletService.AppletVisibilityChanged -= HandleAppletVisibilityChanged;
175
UnregisterDocklets ();
178
void UnregisterDocklets ()
180
UnregisterDockItem (MenuItem);
182
foreach (AbstractDockItem item in DockServices.DockletService.Docklets) {
183
UnregisterDockItem (item);
187
void ResetDockelts ()
189
UnregisterDocklets ();
193
void RegisterDockItem (AbstractDockItem dockItem)
195
dockItem.UpdateNeeded += HandleUpdateNeeded;
196
if (dockItem is IRightClickable)
197
(dockItem as IRightClickable).RemoveClicked += HandleRemoveClicked;
200
void UnregisterDockItem (AbstractDockItem dockItem)
202
dockItem.UpdateNeeded -= HandleUpdateNeeded;
203
if (dockItem is IRightClickable)
204
(dockItem as IRightClickable).RemoveClicked -= HandleRemoveClicked;
207
void HandleAppletVisibilityChanged (object sender, EventArgs e)
210
OnDockItemsChanged ();
213
void HandleAutomaticIconsChanged ()
218
void HandleRemoveClicked (object sender, EventArgs e)
220
if (sender is AbstractDockItem)
221
RemoveItem (sender as AbstractDockItem);
224
void HandleWindowOpened (object o, WindowOpenedArgs args)
226
// we do a delayed update so that we allow a small gap for wnck to catch up
227
if (!args.Window.IsSkipTasklist || args.Window.Pid != System.Diagnostics.Process.GetCurrentProcess ().Id)
231
void HandleWindowClosed (object o, WindowClosedArgs args)
233
if (!args.Window.IsSkipTasklist || args.Window.Pid != System.Diagnostics.Process.GetCurrentProcess ().Id)
237
void HandleUniverseInitialized (object sender, EventArgs e)
239
UpdatesEnabled = true;
243
void HandleUpdateNeeded (object sender, UpdateRequestArgs args)
245
if (!DockItems.Contains (args.Item))
247
if (ItemNeedsUpdate != null)
248
ItemNeedsUpdate (this, args);
253
#region Item Updating
254
AbstractDockItem MaybeCreateCustomItem (ref string identifier)
256
ItemDockItem customItem = null;
258
if (identifier.StartsWith ("file://"))
259
identifier = identifier.Substring ("file://".Length);
261
if (File.Exists (identifier) || Directory.Exists (identifier)) {
262
if (identifier.EndsWith (".desktop")) {
263
bool writeable = true;
265
using (FileStream stream = File.OpenWrite (identifier)) {
271
if (writeable && Path.GetDirectoryName (identifier) != DesktopFilesDirectory) {
272
string newFile = Path.Combine (DesktopFilesDirectory, Path.GetFileName (identifier));
274
Log<ItemsService>.Error ("Could not add custom item with id: {0}, File already exists", identifier);
275
File.Copy (identifier, newFile);
280
identifier = newFile;
282
Item o = Services.UniverseFactory.NewApplicationItem (identifier) as Item;
283
customItem = new ItemDockItem (o);
285
Item o = Services.UniverseFactory.NewFileItem (identifier) as Item;
286
customItem = new ItemDockItem (o);
289
Item e = Services.Core.GetItem (identifier);
291
customItem = new ItemDockItem (e);
293
Log<ItemsService>.Error ("Could not add custom item with id: {0}", identifier);
298
void SimplifyPositions (IEnumerable<AbstractDockItem> items)
301
// we call ToArray so our enumerator does get screwed up when we change the Position
302
foreach (AbstractDockItem item in items.OrderBy (di => di.Position).ToArray ())
306
void DelayUpdateItems ()
308
GLib.Timeout.Add (UpdateDelay, delegate {
319
if (!CustomItemsRead) {
320
foreach (string s in ReadCustomItems ())
321
InternalAddItemToDock (s, LastPosition + 1);
325
Dictionary<string, int> sortDictionary = ReadSortDictionary ();
326
foreach (ItemDockItem item in OrderedItems.Where (di => di is ItemDockItem)) {
327
if (item.Item == null)
330
if (sortDictionary.ContainsKey (item.Item.UniqueId))
331
item.Position = sortDictionary [item.Item.UniqueId];
333
foreach (AbstractDockItem item in Docklets) {
334
if (sortDictionary.ContainsKey (item.GetType ().FullName))
335
item.Position = sortDictionary [item.GetType ().FullName];
338
CustomItemsRead = true;
344
SimplifyPositions (OrderedItems);
345
SimplifyPositions (Docklets);
347
OnDockItemsChanged ();
350
void UpdateStatItems ()
352
List<ItemDockItem> old_items = new List<ItemDockItem> (stat_items.Where (di => di is ItemDockItem)
353
.Cast<ItemDockItem> ());
354
List<ItemDockItem> local_cust = new List<ItemDockItem> (custom_items.Values
355
.Where (di => di is ItemDockItem)
356
.Cast<ItemDockItem> ());
358
stat_items = new List<AbstractDockItem> ();
360
IEnumerable<Item> mostUsedItems = MostUsedItems ();
362
DateTime currentTime = DateTime.UtcNow;
363
foreach (Item item in mostUsedItems.Where (i => !local_cust.Any (ci => ci.Item != null && ci.Item.UniqueId == i.UniqueId))) {
365
if (old_items.Any (di => di.Item == item)) {
366
stat_items.AddRange (old_items
367
.Where (di => di.Item != null && di.Item.UniqueId == item.UniqueId)
368
.Cast<AbstractDockItem> ());
370
ItemDockItem di = new ItemDockItem (item);
371
RegisterDockItem (di);
372
di.DockAddItem = currentTime;
374
int position = LastPosition + 1;
376
// TODO fixme once mono 1.9 support is dropped
377
if (old_items.Any ())
378
position += old_items.Max ((Func<ItemDockItem, int>) (oi => oi.Position));
380
di.Position = position;
385
// potential leak if not all items in stat_items were ItemDockItems!!!
386
foreach (ItemDockItem item in old_items.Where (di => !stat_items.Contains (di))) {
387
UnregisterDockItem (item);
392
void UpdateTaskItems ()
394
foreach (ItemDockItem item in OrderedItems.Where (di => di is ItemDockItem))
395
item.UpdateApplication ();
397
List<ApplicationDockItem> out_items = new List<ApplicationDockItem> ();
399
IEnumerable<Window> knownWindows = OrderedItems
400
.Where (di => di is ItemDockItem)
401
.Cast<ItemDockItem> ()
402
.SelectMany (di => di.Windows);
404
var prunedWindows = WindowUtils.GetWindows ()
405
.Where (w => !w.IsSkipTasklist && !knownWindows.Contains (w))
406
.GroupBy (w => SafeResClass (w));
408
IEnumerable<ApplicationDockItem> apps = WindowUtils.GetWindows ()
409
.Where (w => !w.IsSkipTasklist && !knownWindows.Contains (w))
410
.GroupBy (w => SafeResClass (w))
411
.Select (ws => CreateApplicationDockItem (ws, task_items));
413
apps.ForEach (a => RegisterDockItem (a));
414
out_items.AddRange (apps);
416
foreach (AbstractDockItem item in task_items) {
417
UnregisterDockItem (item);
421
foreach (ApplicationDockItem adi in out_items.OrderBy (di => di.DockAddItem)) {
422
if (adi.Position == 0) // raw and unpositioned items
423
adi.Position = LastPosition + 1;
427
task_items.AddRange (out_items.Cast<AbstractDockItem> ());
430
ApplicationDockItem CreateApplicationDockItem (IEnumerable<Wnck.Window> windows, IEnumerable<AbstractDockItem> lastSet)
432
ApplicationDockItem adi = new ApplicationDockItem (windows);
434
if (lastSet.Any (di => di.Equals (adi))) {
435
AbstractDockItem match = lastSet.Where (di => di.Equals (adi)).First ();
436
adi.DockAddItem = match.DockAddItem;
437
adi.Position = match.Position;
439
adi.DockAddItem = DateTime.UtcNow;
445
string SafeResClass (Wnck.Window window)
447
if (window.ClassGroup != null && window.ClassGroup.ResClass != null)
448
return window.ClassGroup.ResClass;
452
void OnDockItemsChanged ()
454
output_items.Clear ();
456
if (DockItemsChanged != null)
457
DockItemsChanged (DockItems);
461
#region Item Management
462
bool InternalAddItemToDock (Item item, int position)
464
if (!(item is Item)) {
465
Log<ItemsService>.Error ("Could not add {0} to custom items for dock", item.Safe.Name);
469
string id = item.UniqueId;
470
if (custom_items.ContainsKey (id))
473
AbstractDockItem dockItem = new ItemDockItem (item as Item);
474
RegisterDockItem (dockItem);
476
MakeHoleAtPosition (position);
478
dockItem.Position = position;
479
custom_items [id] = dockItem;
484
bool InternalAddItemToDock (string identifier, int position)
486
if (custom_items.ContainsKey (identifier)) return false;
488
AbstractDockItem customItem = MaybeCreateCustomItem (ref identifier);
490
if (customItem == null) return false;
492
RegisterDockItem (customItem);
494
MakeHoleAtPosition (position);
496
customItem.Position = position;
497
custom_items [identifier] = customItem;
503
#region Disk Utilities
506
WriteSortDictionary ();
510
void WriteSortDictionary ()
513
if (File.Exists (SortDictionaryPath))
514
File.Delete (SortDictionaryPath);
516
using (StreamWriter writer = new StreamWriter (SortDictionaryPath)) {
517
foreach (ItemDockItem di in OrderedItems.Where (di => di is ItemDockItem)) {
521
writer.WriteLine ("{0}|{1}", di.Item.UniqueId, di.Position);
523
foreach (AbstractDockItem di in Docklets) {
524
writer.WriteLine ("{0}|{1}", di.GetType ().FullName, di.Position);
527
} catch (Exception e) {
528
Log<ItemsService>.Error ("Could not write out sort items");
529
Log<ItemsService>.Error (e.Message);
533
Dictionary<string, int> ReadSortDictionary ()
535
Dictionary<string, int> sortDictionary = new Dictionary<string, int> ();
537
using (StreamReader reader = new StreamReader (SortDictionaryPath)) {
539
while (!reader.EndOfStream) {
540
line = reader.ReadLine ().Split ('|');
541
sortDictionary [line [0]] = Convert.ToInt32 (line [1]);
544
} catch (FileNotFoundException e) {
545
Log<ItemsService>.Debug ("Sort Dictionary file not present, nothing to add. " + e.Message);
546
} catch (Exception e) {
547
Log<ItemsService>.Error ("Could not deserialize sort dictionary");
549
return sortDictionary;
552
void WriteCustomItems ()
555
using (Stream s = File.OpenWrite (CustomItemsPath)) {
556
BinaryFormatter f = new BinaryFormatter ();
557
f.Serialize (s, custom_items.Keys.ToArray ());
559
} catch (Exception e) {
560
Log<ItemsService>.Error ("Could not serialize custom items");
561
Log<ItemsService>.Error (e.Message);
565
IEnumerable<string> ReadCustomItems ()
569
using (Stream s = File.OpenRead (CustomItemsPath)) {
570
BinaryFormatter f = new BinaryFormatter ();
571
filenames = f.Deserialize (s) as string[];
573
} catch (FileNotFoundException e) {
574
Log<ItemsService>.Debug ("Custom items file not present, nothing to add. " + e.Message);
575
filenames = new string[0];
577
Log<ItemsService>.Error ("Could not deserialize custom items");
578
filenames = new string[0];
584
#region Random Useful Functions
585
bool ItemCanInteractWithPosition (AbstractDockItem item, int position)
587
return (DockItems.Contains (item) || Docklets.Contains (item)) && position >= 0 && position < DockItems.Count;
591
/// Returns the most used items out of GNOME Do and does a tiny bit of filtering and sorting on them
592
/// This is mostly to encourage a better first run experience, but overall this can be improved
595
/// A <see cref="IEnumerable"/> of the most used items from Do's core universe
597
IEnumerable<Item> MostUsedItems ()
600
.GetItemsOrderedByRelevance ()
601
.Where (item => item.GetType ().Name != "SelectedTextItem" &&
602
item.GetType ().Name != "GNOMETrashFileItem")
603
.Where (item => !DockPreferences.ItemBlacklist.Contains (item.UniqueId))
604
.Take (DockPreferences.AutomaticIcons)
605
.OrderByDescending (item => item is IApplicationItem)
606
.ThenBy (item => item.GetType ().Name)
607
.ThenBy (item => item.Safe.Name);
610
void MakeHoleAtPosition (int position)
612
foreach (AbstractDockItem di in OrderedItems) {
613
if (di.Position >= position)
619
#region IItemsService implementation
620
public event DockItemsChangedHandler DockItemsChanged;
621
public event UpdateRequestHandler ItemNeedsUpdate;
623
public bool UpdatesEnabled { get; private set; }
625
public ReadOnlyCollection<AbstractDockItem> DockItems {
627
if (output_items.Count == 0) {
628
output_items.Add (MenuItem);
630
// Add our custom/task/statistical items in one shot
631
output_items.AddRange (OrderedItems);
633
// add a separator and any docklets that are active
634
if (DockServices.DockletService.ActiveDocklets.Any ()) {
635
output_items.Add (Separator);
636
output_items.AddRange (Docklets.OrderBy (docklet => docklet.Position));
639
return readonly_output_items;
643
public void AddItemToDock (Item item)
645
AddItemToDock (item, LastPosition + 1);
648
public void AddItemToDock (string identifier)
650
AddItemToDock (identifier, LastPosition + 1);
653
public void AddItemToDock (Item item, int position)
655
position = DockItems [position].Position;
656
if (InternalAddItemToDock (item, position)) {
659
OnDockItemsChanged ();
663
public void AddItemToDock (string identifier, int position)
665
position = DockItems [position].Position;
666
if (InternalAddItemToDock (identifier, position)) {
669
OnDockItemsChanged ();
673
public bool ItemCanBeMoved (int item)
675
if (item < 0 || item > DockItems.Count)
678
return (OrderedItems.Contains (DockItems [item]) || Docklets.Contains (DockItems [item]));
681
public bool ItemCanBeRemoved (int item)
683
AbstractDockItem adi = DockItems [item];
684
return adi.WindowCount == 0 && ((GetIconSource (adi) == IconSource.Statistics && adi is ItemDockItem) ||
685
(GetIconSource (adi) == IconSource.Custom));
688
public void DropItemOnPosition (AbstractDockItem item, int position)
691
if (!ItemCanInteractWithPosition (item, position)) continue;
693
if (DockItems [position] is TrashDockItem) {
698
if (item is ApplicationDockItem) {
699
ApplicationDockItem adi = item as ApplicationDockItem;
701
if (adi.Launcher == null) continue;
703
Item launcher = adi.Launcher as Item;
704
if (launcher == null)
707
AbstractDockItem newItem = new ItemDockItem (launcher);
709
newItem.Position = item.Position;
710
newItem.DockAddItem = item.DockAddItem;
711
custom_items [launcher.UniqueId] = newItem;
713
RegisterDockItem (newItem);
720
public void MoveItemToPosition (AbstractDockItem item, int position)
722
if (ItemCanInteractWithPosition (item, position))
723
MoveItemToPosition (DockItems.IndexOf (item), position);
726
public void MoveItemToPosition (int item, int position)
728
if (item == position ||
731
position > DockItems.Count ||
732
item > DockItems.Count)
735
IconSource itemSource = GetIconSource (DockItems [item]);
736
IconSource targetSource = GetIconSource (DockItems [position]);
738
if (itemSource == IconSource.Unknown ||
739
targetSource == IconSource.Unknown ||
740
(itemSource == IconSource.Docklet && targetSource != IconSource.Docklet) ||
741
(itemSource != IconSource.Docklet && targetSource == IconSource.Docklet))
744
AbstractDockItem primaryItem = DockItems [item];
745
AbstractDockItem targetItem = DockItems [position];
747
int startPosition = primaryItem.Position;
748
int targetPosition = targetItem.Position;
750
IEnumerable<AbstractDockItem> itemSet;
751
if (itemSource == IconSource.Docklet)
754
itemSet = OrderedItems;
756
foreach (AbstractDockItem di in itemSet) {
757
if (startPosition < targetPosition) {
758
// the item is being shifted to the right. Everything greater than item up to and including target item
759
// needs to be shifted to the left
760
if (di.Position > startPosition && di.Position <= targetPosition)
763
// the item is being shifted to the left. Everthing less than the item and up to and including target item
764
// needs to be shifted to the right
765
if (di.Position < startPosition && di.Position >= targetPosition)
770
primaryItem.Position = targetPosition;
772
OnDockItemsChanged ();
776
public void ForceUpdate ()
781
public IconSource GetIconSource (AbstractDockItem item)
783
if (task_items.Contains (item))
784
return IconSource.Application;
786
if (stat_items.Contains (item))
787
return IconSource.Statistics;
789
if (custom_items.Values.Contains (item))
790
return IconSource.Custom;
792
if (Docklets.Contains (item))
793
return IconSource.Docklet;
795
return IconSource.Unknown;
798
public bool RemoveItem (AbstractDockItem item)
800
if (!DockItems.Contains (item))
802
return RemoveItem (DockItems.IndexOf (item));
805
public bool RemoveItem (int item)
807
bool result = ItemCanBeRemoved (item);
810
if (GetIconSource (DockItems [item]) == IconSource.Statistics) {
811
ItemDockItem di = (DockItems [item] as ItemDockItem);
812
DockPreferences.AddBlacklistItem ((DockItems [item] as ItemDockItem).Item.UniqueId);
813
DockPreferences.AutomaticIcons = Math.Max (0, DockPreferences.AutomaticIcons - 1);
814
} else if (GetIconSource (DockItems [item]) == IconSource.Custom) {
815
KeyValuePair<string, AbstractDockItem> kvp = custom_items
816
.Where (k => k.Value.Equals (DockItems [item]))
819
custom_items.Remove (kvp.Key);
821
if (kvp.Key.EndsWith (".desktop") && Path.GetDirectoryName (kvp.Key) == DesktopFilesDirectory) {
823
File.Delete (kvp.Key);
825
// Don't really care, it was only a nice thing to do...
837
public bool HotSeatItem (AbstractDockItem item, List<AbstractDockItem> seatedItems)
839
Log<ItemsService>.Error ("Items Service cannot currently handle hotseating");
843
public bool ResetHotSeat (AbstractDockItem item)
845
Log<ItemsService>.Error ("Items Service cannot currently handle hotseating");
850
#region IDisposable implementation
851
public void Dispose ()
855
foreach (AbstractDockItem di in DockItems) {
859
UnregisterDockItem (di);
863
if (!Separator.Disposed)
864
Separator.Dispose ();
866
custom_items.Clear ();
868
output_items.Clear ();