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
if (Wnck.Screen.Default != null) {
169
Wnck.Screen.Default.WindowClosed -= HandleWindowClosed;
170
Wnck.Screen.Default.WindowOpened -= HandleWindowOpened;
174
DockPreferences.AutomaticIconsChanged -= HandleAutomaticIconsChanged;
175
DockServices.DockletService.AppletVisibilityChanged -= HandleAppletVisibilityChanged;
177
UnregisterDocklets ();
180
void UnregisterDocklets ()
182
UnregisterDockItem (MenuItem);
184
foreach (AbstractDockItem item in DockServices.DockletService.Docklets) {
185
UnregisterDockItem (item);
189
void ResetDockelts ()
191
UnregisterDocklets ();
195
void RegisterDockItem (AbstractDockItem dockItem)
197
dockItem.UpdateNeeded += HandleUpdateNeeded;
198
if (dockItem is IRightClickable)
199
(dockItem as IRightClickable).RemoveClicked += HandleRemoveClicked;
202
void UnregisterDockItem (AbstractDockItem dockItem)
204
dockItem.UpdateNeeded -= HandleUpdateNeeded;
205
if (dockItem is IRightClickable)
206
(dockItem as IRightClickable).RemoveClicked -= HandleRemoveClicked;
209
void HandleAppletVisibilityChanged (object sender, EventArgs e)
212
OnDockItemsChanged ();
215
void HandleAutomaticIconsChanged ()
220
void HandleRemoveClicked (object sender, EventArgs e)
222
if (sender is AbstractDockItem)
223
RemoveItem (sender as AbstractDockItem);
226
void HandleWindowOpened (object o, WindowOpenedArgs args)
228
// we do a delayed update so that we allow a small gap for wnck to catch up
229
if (!args.Window.IsSkipTasklist || args.Window.Pid != System.Diagnostics.Process.GetCurrentProcess ().Id)
233
void HandleWindowClosed (object o, WindowClosedArgs args)
235
if (!args.Window.IsSkipTasklist || args.Window.Pid != System.Diagnostics.Process.GetCurrentProcess ().Id)
239
void HandleUniverseInitialized (object sender, EventArgs e)
241
UpdatesEnabled = true;
245
void HandleUpdateNeeded (object sender, UpdateRequestArgs args)
247
if (!DockItems.Contains (args.Item))
249
if (ItemNeedsUpdate != null)
250
ItemNeedsUpdate (this, args);
255
#region Item Updating
256
AbstractDockItem MaybeCreateCustomItem (ref string identifier)
258
ItemDockItem customItem = null;
260
if (identifier.StartsWith ("file://"))
261
identifier = identifier.Substring ("file://".Length);
263
if (File.Exists (identifier) || Directory.Exists (identifier)) {
264
if (identifier.EndsWith (".desktop")) {
265
bool writeable = true;
267
using (FileStream stream = File.OpenWrite (identifier)) {
273
if (writeable && Path.GetDirectoryName (identifier) != DesktopFilesDirectory) {
274
string newFile = Path.Combine (DesktopFilesDirectory, Path.GetFileName (identifier));
276
Log<ItemsService>.Error ("Could not add custom item with id: {0}, File already exists", identifier);
277
File.Copy (identifier, newFile);
282
identifier = newFile;
284
Item o = Services.UniverseFactory.NewApplicationItem (identifier) as Item;
285
customItem = new ItemDockItem (o);
287
Item o = Services.UniverseFactory.NewFileItem (identifier) as Item;
288
customItem = new ItemDockItem (o);
291
Item e = Services.Core.GetItem (identifier);
293
customItem = new ItemDockItem (e);
295
Log<ItemsService>.Error ("Could not add custom item with id: {0}", identifier);
300
void SimplifyPositions (IEnumerable<AbstractDockItem> items)
303
// we call ToArray so our enumerator does get screwed up when we change the Position
304
foreach (AbstractDockItem item in items.OrderBy (di => di.Position).ToArray ())
308
void DelayUpdateItems ()
310
GLib.Timeout.Add (UpdateDelay, delegate {
321
if (!CustomItemsRead) {
322
foreach (string s in ReadCustomItems ())
323
InternalAddItemToDock (s, LastPosition + 1);
327
Dictionary<string, int> sortDictionary = ReadSortDictionary ();
328
foreach (ItemDockItem item in OrderedItems.Where (di => di is ItemDockItem)) {
329
if (item.Item == null)
332
if (sortDictionary.ContainsKey (item.Item.UniqueId))
333
item.Position = sortDictionary [item.Item.UniqueId];
335
foreach (AbstractDockItem item in Docklets) {
336
if (sortDictionary.ContainsKey (item.GetType ().FullName))
337
item.Position = sortDictionary [item.GetType ().FullName];
340
CustomItemsRead = true;
346
SimplifyPositions (OrderedItems);
347
SimplifyPositions (Docklets);
349
OnDockItemsChanged ();
352
void UpdateStatItems ()
354
List<ItemDockItem> old_items = new List<ItemDockItem> (stat_items.Where (di => di is ItemDockItem)
355
.Cast<ItemDockItem> ());
356
List<ItemDockItem> local_cust = new List<ItemDockItem> (custom_items.Values
357
.Where (di => di is ItemDockItem)
358
.Cast<ItemDockItem> ());
360
stat_items = new List<AbstractDockItem> ();
362
IEnumerable<Item> mostUsedItems = MostUsedItems ();
364
DateTime currentTime = DateTime.UtcNow;
365
foreach (Item item in mostUsedItems.Where (i => !local_cust.Any (ci => ci.Item != null && ci.Item.UniqueId == i.UniqueId))) {
367
if (old_items.Any (di => di.Item == item)) {
368
stat_items.AddRange (old_items
369
.Where (di => di.Item != null && di.Item.UniqueId == item.UniqueId)
370
.Cast<AbstractDockItem> ());
372
ItemDockItem di = new ItemDockItem (item);
373
RegisterDockItem (di);
374
di.DockAddItem = currentTime;
376
int position = LastPosition + 1;
378
// TODO fixme once mono 1.9 support is dropped
379
if (old_items.Any ())
380
position += old_items.Max ((Func<ItemDockItem, int>) (oi => oi.Position));
382
di.Position = position;
387
// potential leak if not all items in stat_items were ItemDockItems!!!
388
foreach (ItemDockItem item in old_items.Where (di => !stat_items.Contains (di))) {
389
UnregisterDockItem (item);
394
void UpdateTaskItems ()
396
foreach (ItemDockItem item in OrderedItems.Where (di => di is ItemDockItem))
397
item.UpdateApplication ();
399
List<ApplicationDockItem> out_items = new List<ApplicationDockItem> ();
401
IEnumerable<Window> knownWindows = OrderedItems
402
.Where (di => di is ItemDockItem)
403
.Cast<ItemDockItem> ()
404
.SelectMany (di => di.Windows);
406
var prunedWindows = WindowUtils.GetWindows ()
407
.Where (w => !w.IsSkipTasklist && !knownWindows.Contains (w))
408
.GroupBy (w => SafeResClass (w));
410
IEnumerable<ApplicationDockItem> apps = WindowUtils.GetWindows ()
411
.Where (w => !w.IsSkipTasklist && !knownWindows.Contains (w))
412
.GroupBy (w => SafeResClass (w))
413
.Select (ws => CreateApplicationDockItem (ws, task_items));
415
apps.ForEach (a => RegisterDockItem (a));
416
out_items.AddRange (apps);
418
foreach (AbstractDockItem item in task_items) {
419
UnregisterDockItem (item);
423
foreach (ApplicationDockItem adi in out_items.OrderBy (di => di.DockAddItem)) {
424
if (adi.Position == 0) // raw and unpositioned items
425
adi.Position = LastPosition + 1;
429
task_items.AddRange (out_items.Cast<AbstractDockItem> ());
432
ApplicationDockItem CreateApplicationDockItem (IEnumerable<Wnck.Window> windows, IEnumerable<AbstractDockItem> lastSet)
434
ApplicationDockItem adi = new ApplicationDockItem (windows);
436
if (lastSet.Any (di => di.Equals (adi))) {
437
AbstractDockItem match = lastSet.Where (di => di.Equals (adi)).First ();
438
adi.DockAddItem = match.DockAddItem;
439
adi.Position = match.Position;
441
adi.DockAddItem = DateTime.UtcNow;
447
string SafeResClass (Wnck.Window window)
449
if (window.ClassGroup != null && window.ClassGroup.ResClass != null)
450
return window.ClassGroup.ResClass;
454
void OnDockItemsChanged ()
456
output_items.Clear ();
458
if (DockItemsChanged != null)
459
DockItemsChanged (DockItems);
463
#region Item Management
464
bool InternalAddItemToDock (Item item, int position)
466
if (!(item is Item)) {
467
Log<ItemsService>.Error ("Could not add {0} to custom items for dock", item.Safe.Name);
471
string id = item.UniqueId;
472
if (custom_items.ContainsKey (id))
475
AbstractDockItem dockItem = new ItemDockItem (item as Item);
476
RegisterDockItem (dockItem);
478
MakeHoleAtPosition (position);
480
dockItem.Position = position;
481
custom_items [id] = dockItem;
486
bool InternalAddItemToDock (string identifier, int position)
488
if (custom_items.ContainsKey (identifier)) return false;
490
AbstractDockItem customItem = MaybeCreateCustomItem (ref identifier);
492
if (customItem == null) return false;
494
RegisterDockItem (customItem);
496
MakeHoleAtPosition (position);
498
customItem.Position = position;
499
custom_items [identifier] = customItem;
505
#region Disk Utilities
508
WriteSortDictionary ();
512
void WriteSortDictionary ()
515
if (File.Exists (SortDictionaryPath))
516
File.Delete (SortDictionaryPath);
518
using (StreamWriter writer = new StreamWriter (SortDictionaryPath)) {
519
foreach (ItemDockItem di in OrderedItems.Where (di => di is ItemDockItem)) {
523
writer.WriteLine ("{0}|{1}", di.Item.UniqueId, di.Position);
525
foreach (AbstractDockItem di in Docklets) {
526
writer.WriteLine ("{0}|{1}", di.GetType ().FullName, di.Position);
529
} catch (Exception e) {
530
Log<ItemsService>.Error ("Could not write out sort items");
531
Log<ItemsService>.Error (e.Message);
535
Dictionary<string, int> ReadSortDictionary ()
537
Dictionary<string, int> sortDictionary = new Dictionary<string, int> ();
539
using (StreamReader reader = new StreamReader (SortDictionaryPath)) {
541
while (!reader.EndOfStream) {
542
line = reader.ReadLine ().Split ('|');
543
sortDictionary [line [0]] = Convert.ToInt32 (line [1]);
546
} catch (FileNotFoundException e) {
547
Log<ItemsService>.Debug ("Sort Dictionary file not present, nothing to add. " + e.Message);
548
} catch (Exception e) {
549
Log<ItemsService>.Error ("Could not deserialize sort dictionary");
551
return sortDictionary;
554
void WriteCustomItems ()
557
using (Stream s = File.OpenWrite (CustomItemsPath)) {
558
BinaryFormatter f = new BinaryFormatter ();
559
f.Serialize (s, custom_items.Keys.ToArray ());
561
} catch (Exception e) {
562
Log<ItemsService>.Error ("Could not serialize custom items");
563
Log<ItemsService>.Error (e.Message);
567
IEnumerable<string> ReadCustomItems ()
571
using (Stream s = File.OpenRead (CustomItemsPath)) {
572
BinaryFormatter f = new BinaryFormatter ();
573
filenames = f.Deserialize (s) as string[];
575
} catch (FileNotFoundException e) {
576
Log<ItemsService>.Debug ("Custom items file not present, nothing to add. " + e.Message);
577
filenames = new string[0];
579
Log<ItemsService>.Error ("Could not deserialize custom items");
580
filenames = new string[0];
586
#region Random Useful Functions
587
bool ItemCanInteractWithPosition (AbstractDockItem item, int position)
589
return (DockItems.Contains (item) || Docklets.Contains (item)) && position >= 0 && position < DockItems.Count;
593
/// Returns the most used items out of GNOME Do and does a tiny bit of filtering and sorting on them
594
/// This is mostly to encourage a better first run experience, but overall this can be improved
597
/// A <see cref="IEnumerable"/> of the most used items from Do's core universe
599
IEnumerable<Item> MostUsedItems ()
602
.GetItemsOrderedByRelevance ()
603
.Where (item => item.GetType ().Name != "SelectedTextItem" &&
604
item.GetType ().Name != "GNOMETrashFileItem")
605
.Where (item => !DockPreferences.ItemBlacklist.Contains (item.UniqueId))
606
.Take (DockPreferences.AutomaticIcons)
607
.OrderByDescending (item => item is IApplicationItem)
608
.ThenBy (item => item.GetType ().Name)
609
.ThenBy (item => item.Safe.Name);
612
void MakeHoleAtPosition (int position)
614
foreach (AbstractDockItem di in OrderedItems) {
615
if (di.Position >= position)
621
#region IItemsService implementation
622
public event DockItemsChangedHandler DockItemsChanged;
623
public event UpdateRequestHandler ItemNeedsUpdate;
625
public bool UpdatesEnabled { get; private set; }
627
public ReadOnlyCollection<AbstractDockItem> DockItems {
629
if (output_items.Count == 0) {
630
output_items.Add (MenuItem);
632
// Add our custom/task/statistical items in one shot
633
output_items.AddRange (OrderedItems);
635
// add a separator and any docklets that are active
636
if (DockServices.DockletService.ActiveDocklets.Any ()) {
637
output_items.Add (Separator);
638
output_items.AddRange (Docklets.OrderBy (docklet => docklet.Position));
641
return readonly_output_items;
645
public void AddItemToDock (Item item)
647
AddItemToDock (item, LastPosition + 1);
650
public void AddItemToDock (string identifier)
652
AddItemToDock (identifier, LastPosition + 1);
655
public void AddItemToDock (Item item, int position)
657
position = DockItems [position].Position;
658
if (InternalAddItemToDock (item, position)) {
661
OnDockItemsChanged ();
665
public void AddItemToDock (string identifier, int position)
667
position = DockItems [position].Position;
668
if (InternalAddItemToDock (identifier, position)) {
671
OnDockItemsChanged ();
675
public bool ItemCanBeMoved (int item)
677
if (item < 0 || item > DockItems.Count)
680
return (OrderedItems.Contains (DockItems [item]) || Docklets.Contains (DockItems [item]));
683
public bool ItemCanBeRemoved (int item)
685
if (item < 0 || item >= DockItems.Count) return false;
686
AbstractDockItem adi = DockItems [item];
687
return Docklets.Contains (DockItems [item]) ||
688
(adi.WindowCount == 0 && ((GetIconSource (adi) == IconSource.Statistics && adi is ItemDockItem) ||
689
(GetIconSource (adi) == IconSource.Custom)));
692
public void DropItemOnPosition (AbstractDockItem item, int position)
695
if (!ItemCanInteractWithPosition (item, position)) continue;
697
if (DockItems [position] is TrashDockItem && !(item is TrashDockItem)) {
702
if (item is ApplicationDockItem) {
703
ApplicationDockItem adi = item as ApplicationDockItem;
705
if (adi.Launcher == null) continue;
707
Item launcher = adi.Launcher as Item;
708
if (launcher == null)
711
AbstractDockItem newItem = new ItemDockItem (launcher);
713
newItem.Position = item.Position;
714
newItem.DockAddItem = item.DockAddItem;
715
custom_items [launcher.UniqueId] = newItem;
717
RegisterDockItem (newItem);
724
public void MoveItemToPosition (AbstractDockItem item, int position)
726
if (ItemCanInteractWithPosition (item, position))
727
MoveItemToPosition (DockItems.IndexOf (item), position);
730
public void MoveItemToPosition (int item, int position)
732
if (item == position ||
735
position > DockItems.Count ||
736
item > DockItems.Count)
739
IconSource itemSource = GetIconSource (DockItems [item]);
740
IconSource targetSource = GetIconSource (DockItems [position]);
742
if (itemSource == IconSource.Unknown ||
743
targetSource == IconSource.Unknown ||
744
(itemSource == IconSource.Docklet && targetSource != IconSource.Docklet) ||
745
(itemSource != IconSource.Docklet && targetSource == IconSource.Docklet))
748
AbstractDockItem primaryItem = DockItems [item];
749
AbstractDockItem targetItem = DockItems [position];
751
int startPosition = primaryItem.Position;
752
int targetPosition = targetItem.Position;
754
IEnumerable<AbstractDockItem> itemSet;
755
if (itemSource == IconSource.Docklet)
758
itemSet = OrderedItems;
760
foreach (AbstractDockItem di in itemSet) {
761
if (startPosition < targetPosition) {
762
// the item is being shifted to the right. Everything greater than item up to and including target item
763
// needs to be shifted to the left
764
if (di.Position > startPosition && di.Position <= targetPosition)
767
// the item is being shifted to the left. Everthing less than the item and up to and including target item
768
// needs to be shifted to the right
769
if (di.Position < startPosition && di.Position >= targetPosition)
774
primaryItem.Position = targetPosition;
776
OnDockItemsChanged ();
780
public void ForceUpdate ()
785
public IconSource GetIconSource (AbstractDockItem item)
787
if (task_items.Contains (item))
788
return IconSource.Application;
790
if (stat_items.Contains (item))
791
return IconSource.Statistics;
793
if (custom_items.Values.Contains (item))
794
return IconSource.Custom;
796
if (Docklets.Contains (item))
797
return IconSource.Docklet;
799
return IconSource.Unknown;
802
public bool RemoveItem (AbstractDockItem item)
804
if (!DockItems.Contains (item))
806
return RemoveItem (DockItems.IndexOf (item));
809
public bool RemoveItem (int item)
811
bool result = ItemCanBeRemoved (item);
814
if (Docklets.Contains (DockItems [item])) {
815
DockServices.DockletService.ToggleDocklet (DockItems [item] as AbstractDockletItem);
816
} else if (GetIconSource (DockItems [item]) == IconSource.Statistics) {
817
ItemDockItem di = (DockItems [item] as ItemDockItem);
818
DockPreferences.AddBlacklistItem ((DockItems [item] as ItemDockItem).Item.UniqueId);
819
DockPreferences.AutomaticIcons = Math.Max (0, DockPreferences.AutomaticIcons - 1);
820
} else if (GetIconSource (DockItems [item]) == IconSource.Custom) {
821
KeyValuePair<string, AbstractDockItem> kvp = custom_items
822
.Where (k => k.Value.Equals (DockItems [item]))
825
custom_items.Remove (kvp.Key);
827
if (kvp.Key.EndsWith (".desktop") && Path.GetDirectoryName (kvp.Key) == DesktopFilesDirectory) {
829
File.Delete (kvp.Key);
831
// Don't really care, it was only a nice thing to do...
843
public bool HotSeatItem (AbstractDockItem item, List<AbstractDockItem> seatedItems)
845
Log<ItemsService>.Error ("Items Service cannot currently handle hotseating");
849
public bool ResetHotSeat (AbstractDockItem item)
851
Log<ItemsService>.Error ("Items Service cannot currently handle hotseating");
856
#region IDisposable implementation
857
public void Dispose ()
861
foreach (AbstractDockItem di in DockItems) {
865
UnregisterDockItem (di);
869
if (!Separator.Disposed)
870
Separator.Dispose ();
872
custom_items.Clear ();
874
output_items.Clear ();