Merge lp:~docky-core/docky/sound into lp:docky

Proposed by Rico Tzschichholz
Status: Needs review
Proposed branch: lp:~docky-core/docky/sound
Merge into: lp:docky
Diff against target: 4802 lines (+4154/-198)
38 files modified
Docky-2.sln (+7/-0)
Docky.CairoHelper/Cairo/Color_Extensions.cs (+5/-0)
Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs (+35/-0)
Docky.Items/Docky.Items.csproj (+1/-0)
Docky.Items/Docky.Menus/SliderMenuItem.cs (+72/-0)
Docky.Items/Makefile.am (+1/-0)
Docky.Services/Docky.Services/DrawingService.cs (+18/-16)
Docky/Docky.csproj (+2/-0)
Docky/Docky/Menus/DockItemMenu.cs (+17/-11)
Docky/Docky/Menus/MenuItemWidget.cs (+111/-167)
Docky/Docky/Menus/SliderMenuItemWidget.cs (+224/-0)
Docky/Docky/Menus/TextMenuItemWidget.cs (+121/-0)
Docky/Makefile.am (+2/-0)
Docky/gtk-gui/gui.stetic (+1/-4)
StandardPlugins/Makefile.am (+2/-0)
StandardPlugins/Sound/Makefile.am (+45/-0)
StandardPlugins/Sound/Resources/Sound.addin.xml.in (+25/-0)
StandardPlugins/Sound/Sound.csproj (+79/-0)
StandardPlugins/Sound/Sound.dll.config (+6/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs (+133/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs (+118/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs (+655/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs (+65/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs (+76/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs (+73/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs (+509/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs (+89/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs (+120/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs (+346/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs (+179/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs (+84/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs (+184/-0)
StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs (+137/-0)
StandardPlugins/Sound/src/SoundDockItem.cs (+537/-0)
StandardPlugins/Sound/src/SoundItemProvider.cs (+42/-0)
configure.ac (+2/-0)
po/POTFILES.in (+1/-0)
po/docky.pot (+30/-0)
To merge this branch: bzr merge lp:~docky-core/docky/sound
Reviewer Review Type Date Requested Status
Docky Core Pending
Review via email: mp+47506@code.launchpad.net

Description of the change

add Sound docklet and some refactoring of the Menu classes.

pulseaudio-sharp still has some mainloop issues. But it could be considered has a minor problem.

To post a comment you must log in.
Revision history for this message
Alex Chmyr (achmyr) wrote :

 I've merged sound branch changes into mainline and built it, when it
 first time starts, volume icon is a default (white gtk icon), after
 any action (volume change, right-click) it starts show normally, please take a look to logs in bug attached

lp:~docky-core/docky/sound updated
1777. By Rico Tzschichholz

merge trunk 1813

1778. By Rico Tzschichholz

merge trunk 1825

1779. By Rico Tzschichholz

refresh Sound.csproj

Unmerged revisions

1779. By Rico Tzschichholz

refresh Sound.csproj

1778. By Rico Tzschichholz

merge trunk 1825

1777. By Rico Tzschichholz

merge trunk 1813

1776. By Rico Tzschichholz

rename MenuItem property to MItem of MenuItemWidget

1775. By Rico Tzschichholz

revert change in Context_Extension

1774. By Rico Tzschichholz

add Sound docklet

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Docky-2.sln'
2--- Docky-2.sln 2011-12-22 15:47:35 +0000
3+++ Docky-2.sln 2012-02-04 07:53:22 +0000
4@@ -43,6 +43,8 @@
5 EndProject
6 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clippy", "StandardPlugins\Clippy\Clippy.csproj", "{E15414F1-C6F7-4D3E-8D25-BF32F93D3818}"
7 EndProject
8+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sound", "StandardPlugins\Sound\Sound.csproj", "{16B2B0E3-3437-4F07-958D-E961E132F5F5}"
9+EndProject
10 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkMonitor", "StandardPlugins\NetworkMonitor\NetworkMonitor.csproj", "{6FC9E311-1829-440F-9A6C-70302B656156}"
11 EndProject
12 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docky.Widgets", "Docky.Widgets\Docky.Widgets.csproj", "{9CF2F475-7848-4333-9A35-93E8224653B3}"
13@@ -55,6 +57,10 @@
14 Release|Any CPU = Release|Any CPU
15 EndGlobalSection
16 GlobalSection(ProjectConfigurationPlatforms) = postSolution
17+ {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+ {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+ {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
20+ {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Release|Any CPU.Build.0 = Release|Any CPU
21 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Release|Any CPU.ActiveCfg = Release|Any CPU
24@@ -165,6 +171,7 @@
25 {AAD062AA-454A-4263-97E7-B6ED3020D84C} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
26 {66939AAB-0832-4D2D-8282-3E29AA772B69} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
27 {E15414F1-C6F7-4D3E-8D25-BF32F93D3818} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
28+ {16B2B0E3-3437-4F07-958D-E961E132F5F5} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
29 {6FC9E311-1829-440F-9A6C-70302B656156} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
30 EndGlobalSection
31 GlobalSection(MonoDevelopProperties) = preSolution
32
33=== modified file 'Docky.CairoHelper/Cairo/Color_Extensions.cs'
34--- Docky.CairoHelper/Cairo/Color_Extensions.cs 2009-12-06 05:14:01 +0000
35+++ Docky.CairoHelper/Cairo/Color_Extensions.cs 2012-02-04 07:53:22 +0000
36@@ -72,6 +72,11 @@
37 return new Cairo.Color (self.R, self.G, self.B, alpha);
38 }
39
40+ public static Cairo.Color Inverse (this Color self)
41+ {
42+ return new Cairo.Color (1.0 - self.R, 1.0 - self.G, 1.0 - self.B, self.A);
43+ }
44+
45 // Basic Getters
46 public static double GetHue (this Color self)
47 {
48
49=== modified file 'Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs'
50--- Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs 2011-03-13 16:47:29 +0000
51+++ Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs 2012-02-04 07:53:22 +0000
52@@ -76,6 +76,41 @@
53 Internal = image;
54 }
55
56+ DockySurface (Gdk.Pixbuf pixbuf, int width, int height, int offset_x, int offset_y)
57+ {
58+ Width = width;
59+ Height = height;
60+
61+ Gdk.CairoHelper.SetSourcePixbuf (Context, pixbuf, offset_x, offset_y);
62+ Context.Paint ();
63+ }
64+
65+ public DockySurface (Gdk.Pixbuf pixbuf) : this(pixbuf, pixbuf.Width, pixbuf.Height, 0, 0)
66+ {
67+ }
68+
69+ public DockySurface (Gdk.Pixbuf pixbuf, bool square)
70+ {
71+ int width = pixbuf.Width;
72+ int height = pixbuf.Height;
73+ int offset_x = 0;
74+ int offset_y = 0;
75+
76+ if (!square) {
77+ Width = width;
78+ Height = height;
79+ } else {
80+ int size = (width >= height ? width : height);
81+ offset_x = (size - width) / 2;
82+ offset_y = (size - height) / 2;
83+ Width = size;
84+ Height = size;
85+ }
86+
87+ Gdk.CairoHelper.SetSourcePixbuf (Context, pixbuf, offset_x, offset_y);
88+ Context.Paint ();
89+ }
90+
91 public void ResetContext ()
92 {
93 if (context == null)
94
95=== modified file 'Docky.Items/Docky.Items.csproj'
96--- Docky.Items/Docky.Items.csproj 2011-12-22 15:47:35 +0000
97+++ Docky.Items/Docky.Items.csproj 2012-02-04 07:53:22 +0000
98@@ -98,6 +98,7 @@
99 <Compile Include="Docky.Menus\MenuList.cs" />
100 <Compile Include="Docky.Menus\IconMenuItem.cs" />
101 <Compile Include="Docky.Items\IconEmblem.cs" />
102+ <Compile Include="Docky.Menus\SliderMenuItem.cs" />
103 </ItemGroup>
104 <ItemGroup>
105 <ProjectReference Include="..\Docky.Services\Docky.Services.csproj">
106
107=== added file 'Docky.Items/Docky.Menus/SliderMenuItem.cs'
108--- Docky.Items/Docky.Menus/SliderMenuItem.cs 1970-01-01 00:00:00 +0000
109+++ Docky.Items/Docky.Menus/SliderMenuItem.cs 2012-02-04 07:53:22 +0000
110@@ -0,0 +1,72 @@
111+//
112+// Copyright (C) 2011 Rico Tzschichholz
113+//
114+// This program is free software: you can redistribute it and/or modify
115+// it under the terms of the GNU General Public License as published by
116+// the Free Software Foundation, either version 3 of the License, or
117+// (at your option) any later version.
118+//
119+// This program is distributed in the hope that it will be useful,
120+// but WITHOUT ANY WARRANTY; without even the implied warranty of
121+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
122+// GNU General Public License for more details.
123+//
124+// You should have received a copy of the GNU General Public License
125+// along with this program. If not, see <http://www.gnu.org/licenses/>.
126+//
127+
128+using System;
129+
130+namespace Docky.Menus
131+{
132+
133+ public class PositionChangedEventArgs : EventArgs
134+ {
135+ public double NewPosition { get; private set; }
136+
137+ public PositionChangedEventArgs (double new_position)
138+ {
139+ NewPosition = new_position;
140+ }
141+ }
142+
143+ public class SliderMenuItem : MenuItem
144+ {
145+ public event EventHandler<PositionChangedEventArgs> PositionChanged;
146+
147+ double position;
148+ public double Position {
149+ get { return position; }
150+ set {
151+ if (position == value)
152+ return;
153+ position = value;
154+ if (PositionChanged != null)
155+ PositionChanged (this, new PositionChangedEventArgs (position));
156+ }
157+ }
158+
159+ public SliderMenuItem ()
160+ {
161+ }
162+
163+ public SliderMenuItem (string text, double position, string icon) : this (text, position, icon, false)
164+ {
165+ }
166+
167+ public SliderMenuItem (string text, double position, string icon, bool disabled) : base (text, icon, disabled)
168+ {
169+ this.position = position;
170+ }
171+
172+ public SliderMenuItem (string text, double position, string icon, EventHandler<PositionChangedEventArgs> onSlide) : this(text, position, icon)
173+ {
174+ PositionChanged += onSlide;
175+ }
176+
177+ public SliderMenuItem (string text, double position, string icon, EventHandler<PositionChangedEventArgs> onSlide, bool disabled) : this(text, position, icon, disabled)
178+ {
179+ PositionChanged += onSlide;
180+ }
181+ }
182+}
183
184=== modified file 'Docky.Items/Makefile.am'
185--- Docky.Items/Makefile.am 2011-01-02 12:42:36 +0000
186+++ Docky.Items/Makefile.am 2012-02-04 07:53:22 +0000
187@@ -15,6 +15,7 @@
188 Docky.Menus/ProxyMenuItem.cs \
189 Docky.Menus/MenuList.cs \
190 Docky.Menus/SeparatorMenuItem.cs \
191+ Docky.Menus/SliderMenuItem.cs \
192 Docky.Menus/WindowMenuItem.cs \
193 Docky.Menus/IconMenuItem.cs \
194 Docky.Items/WindowDockItem.cs \
195
196=== modified file 'Docky.Services/Docky.Services/DrawingService.cs'
197--- Docky.Services/Docky.Services/DrawingService.cs 2011-01-16 10:10:00 +0000
198+++ Docky.Services/Docky.Services/DrawingService.cs 2012-02-04 07:53:22 +0000
199@@ -172,7 +172,23 @@
200 }
201
202 /// <summary>
203- /// Load an icon specifying only the size. Size will be used for both width and height.
204+ /// Load an icon at its native size. Note when this is used on icons that are not files or resources
205+ /// an exception will be thrown.
206+ /// </summary>
207+ /// <param name="names">
208+ /// A <see cref="System.String"/>
209+ /// </param>
210+ /// <returns>
211+ /// A <see cref="Gdk.Pixbuf"/>
212+ /// A <see cref="Docky.CairoHelper.DockySurface"/>
213+ /// </returns>
214+ public Gdk.Pixbuf LoadIcon (string names)
215+ {
216+ return LoadIcon (names, -1, -1);
217+ }
218+
219+ /// <summary>
220+ /// Load an icon specifying only the size. Size will be used for both width and height.
221 /// </summary>
222 /// <param name="names">
223 /// A <see cref="System.String"/>
224@@ -182,6 +198,7 @@
225 /// </param>
226 /// <returns>
227 /// A <see cref="Gdk.Pixbuf"/>
228+ /// A <see cref="Docky.CairoHelper.DockySurface"/>
229 /// </returns>
230 public Gdk.Pixbuf LoadIcon (string names, int size)
231 {
232@@ -189,21 +206,6 @@
233 }
234
235 /// <summary>
236- /// Load an icon at its native size. Note when this is used on icons that are not files or resources
237- /// an exception will be thrown.
238- /// </summary>
239- /// <param name="names">
240- /// A <see cref="System.String"/>
241- /// </param>
242- /// <returns>
243- /// A <see cref="Gdk.Pixbuf"/>
244- /// </returns>
245- public Gdk.Pixbuf LoadIcon (string names)
246- {
247- return LoadIcon (names, -1);
248- }
249-
250- /// <summary>
251 /// Returns the string name of the supplied icon.
252 /// </summary>
253 /// <param name="icon">
254
255=== modified file 'Docky/Docky.csproj'
256--- Docky/Docky.csproj 2011-12-22 15:47:35 +0000
257+++ Docky/Docky.csproj 2012-02-04 07:53:22 +0000
258@@ -80,6 +80,8 @@
259 <Compile Include="gtk-gui\Docky.ConfigurationWindow.cs" />
260 <Compile Include="Docky\DockletTile.cs" />
261 <Compile Include="Mono\Options.cs" />
262+ <Compile Include="Docky\Menus\SliderMenuItemWidget.cs" />
263+ <Compile Include="Docky\Menus\TextMenuItemWidget.cs" />
264 </ItemGroup>
265 <ItemGroup>
266 <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
267
268=== modified file 'Docky/Docky/Menus/DockItemMenu.cs'
269--- Docky/Docky/Menus/DockItemMenu.cs 2010-10-15 15:32:36 +0000
270+++ Docky/Docky/Menus/DockItemMenu.cs 2012-02-04 07:53:22 +0000
271@@ -94,7 +94,7 @@
272 selected = NumberItems () - 1;
273
274 UpdateSelected (selected);
275- } while (GetSelectedItem ().item.Disabled);
276+ } while (GetSelectedItem ().MItem.Disabled);
277 } else if (evnt.Key == Gdk.Key.Down) {
278 do {
279 int selected = GetSelected () + 1;
280@@ -103,9 +103,9 @@
281 selected = 0;
282
283 UpdateSelected (selected);
284- } while (GetSelectedItem ().item.Disabled);
285+ } while (GetSelectedItem ().MItem.Disabled);
286 } else if (evnt.Key == Gdk.Key.Return && GetSelectedItem () != null) {
287- MenuItem item = (GetSelectedItem () as MenuItemWidget).item;
288+ MenuItem item = (GetSelectedItem () as MenuItemWidget).MItem;
289 if (!item.Disabled) {
290 item.SendClick ();
291 Hide ();
292@@ -115,7 +115,7 @@
293 } else {
294 foreach (Gtk.Widget widget in (Container.Child as VBox).Children)
295 if (widget is MenuItemWidget) {
296- MenuItem item = (widget as MenuItemWidget).item;
297+ MenuItem item = (widget as MenuItemWidget).MItem;
298 if (evnt.KeyValue == item.Mnemonic)
299 if (!item.Disabled) {
300 item.SendClick ();
301@@ -177,16 +177,22 @@
302
303 first = false;
304 vbox.PackStart (widget);
305- } else {
306- MenuItemWidget menuItem = new MenuItemWidget (item);
307- menuItem.SelectedChanged += HandleSelectedChanged;
308- menuItem.TextColor = textColor;
309- menuItem.MenuShowingIcons = hasIcon;
310+ } else if (item is SliderMenuItem || item is MenuItem) {
311+ MenuItemWidget widget;
312+
313+ if (item is SliderMenuItem)
314+ widget = new SliderMenuItemWidget (item as SliderMenuItem);
315+ else
316+ widget = new TextMenuItemWidget (item);
317+
318+ widget.SelectedChanged += HandleSelectedChanged;
319+ widget.TextColor = textColor;
320+ widget.MenuShowingIcons = hasIcon;
321
322 first = false;
323- vbox.PackStart (menuItem, false, false, 0);
324+ vbox.PackStart (widget, false, false, 0);
325
326- width = Math.Max (width, menuItem.RequestedWidth);
327+ width = Math.Max (width, widget.RequestedWidth);
328 }
329 }
330 vbox.SetSizeRequest (width, -1);
331
332=== modified file 'Docky/Docky/Menus/MenuItemWidget.cs'
333--- Docky/Docky/Menus/MenuItemWidget.cs 2011-02-08 07:28:57 +0000
334+++ Docky/Docky/Menus/MenuItemWidget.cs 2012-02-04 07:53:22 +0000
335@@ -1,6 +1,7 @@
336 //
337 // Copyright (C) 2009 Jason Smith, Robert Dyer
338 // Copyright (C) 2010 Robert Dyer
339+// Copyright (C) 2011 Rico Tzschichholz
340 //
341 // This program is free software: you can redistribute it and/or modify
342 // it under the terms of the GNU General Public License as published by
343@@ -28,22 +29,32 @@
344
345 namespace Docky.Menus
346 {
347- internal class MenuItemWidget : Gtk.EventBox
348+ internal abstract class MenuItemWidget : Gtk.EventBox
349 {
350- const int MenuHeight = 22;
351- const int MinWidth = 100;
352- const int MaxWidth = 350;
353- const int FontSize = 11;
354- const int Padding = 4;
355- const int IconBuffer = Padding - 1;
356-
357- public MenuItem item;
358+ public static MenuItemWidget NewFromMenuItem (MenuItem item)
359+ {
360+ return new TextMenuItemWidget (item);
361+ }
362+
363+ public static MenuItemWidget NewFromMenuItem (SliderMenuItem item)
364+ {
365+ return new SliderMenuItemWidget (item);
366+ }
367+
368+ protected const int MenuHeight = 22;
369+ protected const int MinWidth = 100;
370+ protected const int MaxWidth = 350;
371+ protected const int FontSize = 11;
372+ protected const int Padding = 4;
373+ protected const int IconBuffer = Padding - 1;
374+
375+ public MenuItem MItem { get; protected set; }
376
377 public event EventHandler SelectedChanged;
378
379 public bool Selected { get; set; }
380
381- bool menu_icons = false;
382+ protected bool menu_icons = false;
383 public bool MenuShowingIcons {
384 get {
385 return menu_icons;
386@@ -58,18 +69,18 @@
387
388 public Cairo.Color TextColor { get; set; }
389
390- int TextWidth { get; set; }
391+ protected int TextWidth { get; set; }
392 public int RequestedWidth { get; protected set; }
393
394- DockySurface icon_surface, emblem_surface;
395+ protected DockySurface icon_surface, emblem_surface;
396
397 internal MenuItemWidget (MenuItem item) : base()
398 {
399 TextColor = new Cairo.Color (1, 1, 1);
400- this.item = item;
401- item.IconChanged += ItemIconChanged;
402- item.TextChanged += ItemTextChanged;
403- item.DisabledChanged += ItemDisabledChanged;
404+ MItem = item;
405+ MItem.IconChanged += ItemIconChanged;
406+ MItem.TextChanged += ItemTextChanged;
407+ MItem.DisabledChanged += ItemDisabledChanged;
408
409 AddEvents ((int) Gdk.EventMask.AllEventsMask);
410
411@@ -77,10 +88,10 @@
412 VisibleWindow = false;
413 AboveChild = true;
414
415- CalcTextWidth ();
416+ SetSize ();
417 }
418
419- void SetSize ()
420+ protected void SetSize ()
421 {
422 RequestedWidth = TextWidth + 2 * Padding + 1;
423 if (MenuShowingIcons)
424@@ -89,45 +100,17 @@
425 SetSizeRequest (RequestedWidth, MenuHeight);
426 }
427
428- void CalcTextWidth ()
429- {
430- using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
431- char accel;
432-
433- string text = GLib.Markup.EscapeText (item.Text.Replace ("\n", ""));
434- if (item.Mnemonic.HasValue)
435- layout.SetMarkupWithAccel (text, '_', out accel);
436- else
437- layout.SetMarkup (text);
438- layout.FontDescription = Style.FontDescription;
439- layout.Ellipsize = Pango.EllipsizeMode.End;
440- layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
441- layout.FontDescription.Weight = Pango.Weight.Bold;
442-
443- Pango.Rectangle logical, ink;
444- layout.GetPixelExtents (out ink, out logical);
445-
446- TextWidth = Math.Min (MaxWidth, Math.Max (MinWidth, logical.Width));
447- HasTooltip = TextWidth < logical.Width;
448-
449- layout.Context.Dispose ();
450- }
451-
452- SetSize ();
453- }
454-
455- void ItemDisabledChanged (object sender, EventArgs e)
456- {
457- QueueDraw ();
458- }
459-
460- void ItemTextChanged (object sender, EventArgs e)
461- {
462- CalcTextWidth ();
463- QueueDraw ();
464- }
465-
466- void ItemIconChanged (object sender, EventArgs e)
467+ protected virtual void ItemDisabledChanged (object sender, EventArgs e)
468+ {
469+ QueueDraw ();
470+ }
471+
472+ protected virtual void ItemTextChanged (object sender, EventArgs e)
473+ {
474+ QueueDraw ();
475+ }
476+
477+ protected virtual void ItemIconChanged (object sender, EventArgs e)
478 {
479 if (icon_surface != null)
480 icon_surface.Dispose ();
481@@ -136,19 +119,24 @@
482 QueueDraw ();
483 }
484
485+ protected void OnSelectedChanged ()
486+ {
487+ if (SelectedChanged != null)
488+ SelectedChanged (this, EventArgs.Empty);
489+ }
490+
491 protected override bool OnButtonReleaseEvent (EventButton evnt)
492 {
493- if (!item.Disabled)
494- item.SendClick ();
495- return item.Disabled;
496+ if (!MItem.Disabled)
497+ MItem.SendClick ();
498+ return MItem.Disabled;
499 }
500
501 protected override bool OnMotionNotifyEvent (EventMotion evnt)
502 {
503- if (!item.Disabled && !Selected) {
504+ if (!MItem.Disabled && !Selected) {
505 Selected = true;
506- if (SelectedChanged != null)
507- SelectedChanged (this, EventArgs.Empty);
508+ OnSelectedChanged ();
509 QueueDraw ();
510 }
511 return false;
512@@ -156,10 +144,9 @@
513
514 protected override bool OnEnterNotifyEvent (EventCrossing evnt)
515 {
516- if (!item.Disabled && !Selected) {
517+ if (!MItem.Disabled && !Selected) {
518 Selected = true;
519- if (SelectedChanged != null)
520- SelectedChanged (this, EventArgs.Empty);
521+ OnSelectedChanged ();
522 QueueDraw ();
523 }
524 return false;
525@@ -167,10 +154,9 @@
526
527 protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
528 {
529- if (!item.Disabled && Selected) {
530+ if (!MItem.Disabled && Selected) {
531 Selected = false;
532- if (SelectedChanged != null)
533- SelectedChanged (this, EventArgs.Empty);
534+ OnSelectedChanged ();
535 QueueDraw ();
536 }
537 return base.OnLeaveNotifyEvent (evnt);
538@@ -178,32 +164,68 @@
539
540 protected override bool OnQueryTooltip (int x, int y, bool keyboard_tooltip, Tooltip tooltip)
541 {
542- tooltip.Text = item.Text;
543+ tooltip.Text = MItem.Text;
544 return true;
545 }
546
547- void PlaceSurface (Context cr, DockySurface surface, Gdk.Rectangle allocation)
548+ protected void DrawMenuItemIcon (Context cr)
549 {
550+ Gdk.Rectangle allocation = Allocation;
551+
552 int iconSize = allocation.Height - IconBuffer * 2;
553-
554- int x = allocation.X + Padding + ((iconSize - surface.Width) / 2);
555- int y = allocation.Y + IconBuffer + ((iconSize - surface.Height) / 2);
556-
557- cr.SetSource (surface.Internal, x, y);
558+ int x = allocation.X + Padding;
559+ int y = allocation.Y + IconBuffer;
560+
561+ if (icon_surface == null || (icon_surface.Height != iconSize && icon_surface.Width != iconSize)) {
562+ if (icon_surface != null)
563+ icon_surface.Dispose ();
564+ icon_surface = null;
565+ if (MItem.ForcePixbuf == null)
566+ icon_surface = LoadIcon (MItem.Icon, iconSize);
567+ else
568+ icon_surface = LoadIcon (MItem.ForcePixbuf, iconSize);
569+ }
570+ if (!string.IsNullOrEmpty (MItem.Emblem)
571+ && (emblem_surface == null || (emblem_surface.Height != iconSize && emblem_surface.Width != iconSize))) {
572+ if (emblem_surface != null)
573+ emblem_surface.Dispose ();
574+ emblem_surface = null;
575+ if (!string.IsNullOrEmpty (MItem.Emblem))
576+ emblem_surface = LoadIcon (MItem.Emblem, iconSize);
577+ }
578+
579+ if (icon_surface == null)
580+ return;
581+
582+ cr.Save ();
583+
584+ cr.SetSource (icon_surface.Internal, x, y);
585+ cr.PaintWithAlpha (MItem.Disabled ? 0.5 : 1);
586+
587+ if (MItem.Bold) {
588+ cr.Operator = Operator.Add;
589+ cr.SetSource (icon_surface.Internal, x, y);
590+ cr.PaintWithAlpha (.8);
591+ cr.Operator = Operator.Over;
592+ }
593+
594+ if (emblem_surface != null) {
595+ cr.SetSource (emblem_surface.Internal, x, y);
596+ cr.Paint ();
597+ }
598+
599+ cr.Restore ();
600 }
601
602- DockySurface LoadIcon (Pixbuf icon, int size)
603+ protected DockySurface LoadIcon (Gdk.Pixbuf pbuf, int size)
604 {
605- DockySurface surface;
606- using (Gdk.Pixbuf pixbuf = icon.Copy ().ARScale (size, size)) {
607- surface = new DockySurface (pixbuf.Width, pixbuf.Height);
608- Gdk.CairoHelper.SetSourcePixbuf (surface.Context, pixbuf, 0, 0);
609- surface.Context.Paint ();
610- }
611+ Gdk.Pixbuf pixbuf = pbuf.Copy ().ARScale (size, size);
612+ DockySurface surface = new DockySurface (pixbuf, true);
613+ pixbuf.Dispose ();
614 return surface;
615 }
616
617- DockySurface LoadIcon (string icon, int size)
618+ protected DockySurface LoadIcon (string icon, int size)
619 {
620 bool monochrome = icon.StartsWith ("[monochrome]");
621 if (monochrome)
622@@ -225,85 +247,7 @@
623 return surface;
624 }
625
626- protected override bool OnExposeEvent (EventExpose evnt)
627- {
628- if (!IsRealized)
629- return false;
630-
631- Gdk.Rectangle allocation = Allocation;
632-
633- int pixbufSize = allocation.Height - IconBuffer * 2;
634- if (item.ShowIcons && (icon_surface == null || (icon_surface.Height != pixbufSize && icon_surface.Width != pixbufSize))) {
635- if (icon_surface != null)
636- icon_surface.Dispose ();
637- if (emblem_surface != null)
638- emblem_surface.Dispose ();
639-
640- if (item.ForcePixbuf == null)
641- icon_surface = LoadIcon (item.Icon, pixbufSize);
642- else
643- icon_surface = LoadIcon (item.ForcePixbuf, pixbufSize);
644-
645- if (!string.IsNullOrEmpty (item.Emblem))
646- emblem_surface = LoadIcon (item.Emblem, pixbufSize);
647- }
648-
649- using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
650- if (Selected && !item.Disabled) {
651- cr.Rectangle (allocation.X, allocation.Y, allocation.Width, allocation.Height);
652- cr.Color = TextColor.SetAlpha (.1);
653- cr.Fill ();
654- }
655-
656- if (item.ShowIcons) {
657- PlaceSurface (cr, icon_surface, allocation);
658- cr.PaintWithAlpha (item.Disabled ? 0.5 : 1);
659-
660- if (item.Bold) {
661- cr.Operator = Operator.Add;
662- PlaceSurface (cr, icon_surface, allocation);
663- cr.PaintWithAlpha (.8);
664- cr.Operator = Operator.Over;
665- }
666-
667- if (!string.IsNullOrEmpty (item.Emblem)) {
668- PlaceSurface (cr, emblem_surface, allocation);
669- cr.Paint ();
670- }
671- }
672-
673- using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
674- char accel;
675- string text = GLib.Markup.EscapeText (item.Text.Replace ("\n", ""));
676- if (item.Mnemonic.HasValue)
677- layout.SetMarkupWithAccel (text, '_', out accel);
678- else
679- layout.SetMarkup (text);
680- layout.Width = Pango.Units.FromPixels (TextWidth);
681- layout.FontDescription = Style.FontDescription;
682- layout.Ellipsize = Pango.EllipsizeMode.End;
683- layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
684- layout.FontDescription.Weight = Pango.Weight.Bold;
685-
686- Pango.Rectangle logical, ink;
687- layout.GetPixelExtents (out ink, out logical);
688-
689- int offset = Padding;
690- if (MenuShowingIcons)
691- offset += MenuHeight + Padding;
692- cr.MoveTo (allocation.X + offset, allocation.Y + (allocation.Height - logical.Height) / 2);
693- Pango.CairoHelper.LayoutPath (cr, layout);
694- cr.Color = TextColor.SetAlpha (item.Disabled ? 0.5 : 1);
695- cr.Fill ();
696-
697- layout.Context.Dispose ();
698- }
699-
700- (cr.Target as IDisposable).Dispose ();
701- }
702-
703- return true;
704- }
705+ protected abstract override bool OnExposeEvent (EventExpose evnt);
706
707 public override void Dispose ()
708 {
709@@ -315,10 +259,10 @@
710 emblem_surface.Dispose ();
711 emblem_surface = null;
712
713- item.IconChanged -= ItemIconChanged;
714- item.TextChanged -= ItemTextChanged;
715- item.DisabledChanged -= ItemDisabledChanged;
716- item = null;
717+ MItem.IconChanged -= ItemIconChanged;
718+ MItem.TextChanged -= ItemTextChanged;
719+ MItem.DisabledChanged -= ItemDisabledChanged;
720+ MItem = null;
721
722 base.Dispose ();
723 }
724
725=== added file 'Docky/Docky/Menus/SliderMenuItemWidget.cs'
726--- Docky/Docky/Menus/SliderMenuItemWidget.cs 1970-01-01 00:00:00 +0000
727+++ Docky/Docky/Menus/SliderMenuItemWidget.cs 2012-02-04 07:53:22 +0000
728@@ -0,0 +1,224 @@
729+//
730+// Copyright (C) 2011 Rico Tzschichholz
731+//
732+// This program is free software: you can redistribute it and/or modify
733+// it under the terms of the GNU General Public License as published by
734+// the Free Software Foundation, either version 3 of the License, or
735+// (at your option) any later version.
736+//
737+// This program is distributed in the hope that it will be useful,
738+// but WITHOUT ANY WARRANTY; without even the implied warranty of
739+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
740+// GNU General Public License for more details.
741+//
742+// You should have received a copy of the GNU General Public License
743+// along with this program. If not, see <http://www.gnu.org/licenses/>.
744+//
745+
746+using System;
747+using System.Collections.Generic;
748+
749+using Cairo;
750+using Gdk;
751+using Gtk;
752+
753+using Docky.Services;
754+using Docky.CairoHelper;
755+
756+namespace Docky.Menus
757+{
758+ internal class SliderMenuItemWidget : MenuItemWidget
759+ {
760+ const int Size = 16;
761+ const double StepSize = 0.03;
762+ const bool Highlight = true;
763+
764+ Gdk.Rectangle SliderArea { get; set; }
765+ Gdk.Rectangle DragArea { get; set; }
766+ bool DragActive { get; set; }
767+
768+ internal SliderMenuItemWidget (SliderMenuItem item) : base(item)
769+ {
770+ item.PositionChanged += ItemPositionChanged;
771+ HasTooltip = !string.IsNullOrEmpty (item.Text);
772+ SliderArea = Gdk.Rectangle.Zero;
773+ DragArea = Gdk.Rectangle.Zero;
774+ }
775+
776+ void SetPositionFromCursor (double x, double y)
777+ {
778+ (MItem as SliderMenuItem).Position =
779+ Math.Max (0, Math.Min (1.0, 1.0 - (double) (SliderArea.X + SliderArea.Width - Size / 2 - x)
780+ / (SliderArea.Width - Size)));
781+ }
782+
783+ void ItemPositionChanged (object sender, PositionChangedEventArgs e)
784+ {
785+ QueueDraw ();
786+ }
787+
788+ protected override void ItemTextChanged (object sender, EventArgs e)
789+ {
790+ HasTooltip = !string.IsNullOrEmpty (MItem.Text);
791+ QueueDraw ();
792+ }
793+
794+ protected override bool OnButtonPressEvent (EventButton evnt)
795+ {
796+ if (!SliderArea.Contains ((int) evnt.X, (int) evnt.Y))
797+ return false;
798+
799+ if (DragArea.Contains ((int) evnt.X, (int) evnt.Y))
800+ DragActive = true;
801+
802+ SetPositionFromCursor (evnt.X, evnt.Y);
803+
804+ return false;
805+ }
806+
807+ protected override bool OnButtonReleaseEvent (EventButton evnt)
808+ {
809+ if (MItem.Disabled)
810+ return false;
811+
812+ DragActive = false;
813+
814+ return true;
815+ }
816+
817+ protected override bool OnMotionNotifyEvent (EventMotion evnt)
818+ {
819+ base.OnMotionNotifyEvent (evnt);
820+
821+ if (DragActive)
822+ SetPositionFromCursor (evnt.X, evnt.Y);
823+
824+ return false;
825+ }
826+
827+ protected override bool OnScrollEvent (EventScroll evnt)
828+ {
829+ if (!SliderArea.Contains ((int) evnt.X, (int) evnt.Y))
830+ return false;
831+
832+ switch (evnt.Direction) {
833+ case ScrollDirection.Down:
834+ case ScrollDirection.Left:
835+ (MItem as SliderMenuItem).Position =
836+ Math.Max (0.0, Math.Min (1.0, (MItem as SliderMenuItem).Position - StepSize));
837+ break;
838+ case ScrollDirection.Up:
839+ case ScrollDirection.Right:
840+ (MItem as SliderMenuItem).Position =
841+ Math.Max (0.0, Math.Min (1.0, (MItem as SliderMenuItem).Position + StepSize));
842+ break;
843+ }
844+
845+ QueueDraw ();
846+ return false;
847+ }
848+
849+ protected override bool OnExposeEvent (EventExpose evnt)
850+ {
851+ if (!IsRealized)
852+ return false;
853+
854+ Gdk.Rectangle allocation = Allocation;
855+
856+ using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
857+ if (MItem.ShowIcons)
858+ DrawMenuItemIcon (cr);
859+
860+ int offset = Padding;
861+ if (MenuShowingIcons)
862+ offset += MenuHeight + Padding;
863+
864+ LinearGradient lg;
865+ RadialGradient rg;
866+ Cairo.Color white = new Cairo.Color (1, 1, 1, 1);
867+ Cairo.Color black = new Cairo.Color (0, 0, 0, 1);
868+
869+ double x = (int)(allocation.X + offset) + 0.5;
870+ double y = (int)(allocation.Y + (MenuHeight - Size / 2.0) / 2.0) + 0.5;
871+ double sliderwidth = allocation.Width - offset - Padding;
872+ double sliderheight = Size / 2.0;
873+ double px = (int)(allocation.X + offset + (allocation.Width - offset - Padding - Size)
874+ * (MItem as SliderMenuItem).Position) + 0.5;
875+ double py = (int)(allocation.Y + (MenuHeight - Size) / 2.0) + 0.5;
876+
877+ cr.LineWidth = 1.0;
878+
879+ // draw marker
880+ cr.RoundedRectangle (px, py, Size, Size, Size / 2.0);
881+ rg = new RadialGradient (px + 1.66 * Size / 3.0, py + 1.66 * Size / 3.0, 0,
882+ px + Size / 2.0, py + Size / 2.0, Size / 2.0);
883+ rg.AddColorStop (0, white.SetAlpha (MItem.Disabled ? 0.4 : Selected ? 0.7 : 0.6));
884+ rg.AddColorStop (1, black.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.6 : 0.4));
885+ cr.Pattern = rg;
886+ cr.FillPreserve ();
887+ rg.Destroy ();
888+ cr.RoundedRectangle (x, y, sliderwidth, sliderheight, 0);
889+ cr.FillRule = Cairo.FillRule.EvenOdd;
890+ cr.Clip ();
891+
892+ // background gradient round-box
893+ cr.RoundedRectangle (x + 1, y + 1, sliderwidth - 2, sliderheight - 2, 4.5);
894+ lg = new LinearGradient (x + 1, y + 1, x + 1, y + Size / 2.0);
895+ lg.AddColorStop (0, black.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.3 : 0.25));
896+ lg.AddColorStop (1, black.SetAlpha (MItem.Disabled ? 0.05 : Selected ? 0.1 : 0.05));
897+ cr.Pattern = lg;
898+ cr.FillPreserve ();
899+ lg.Destroy ();
900+ // dark middle stroke
901+ cr.Color = black.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.4 : 0.25);
902+ cr.Stroke ();
903+
904+ // light outer stroke
905+ cr.RoundedRectangle (x, y, sliderwidth, sliderheight, 5.0);
906+ lg = new LinearGradient (x, y, x, y + Size / 2.0);
907+ lg.AddColorStop (0, white.SetAlpha (0));
908+ lg.AddColorStop (1, white.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.5 : 0.3));
909+ cr.Pattern = lg;
910+ cr.Stroke ();
911+ lg.Destroy ();
912+
913+ // dark inner stroke
914+ cr.RoundedRectangle (x + 2, y + 2, sliderwidth - 4, sliderheight - 4, 4.0);
915+ lg = new LinearGradient (x + 2, y + 2, x + 2, y + 2 + Size / 2.0);
916+ lg.AddColorStop (0, black.SetAlpha (MItem.Disabled ? 0.05 : Selected ? 0.05 : 0.1));
917+ lg.AddColorStop (1, black.SetAlpha (0));
918+ cr.Pattern = lg;
919+ cr.Stroke ();
920+ lg.Destroy ();
921+
922+ // colored indication of value
923+ if (Highlight) {
924+ Gdk.Color gdkColor = Style.Backgrounds [(int) StateType.Selected].SetMinimumValue (90);
925+ Cairo.Color highlightColor = new Cairo.Color ((double) gdkColor.Red / ushort.MaxValue,
926+ (double) gdkColor.Green / ushort.MaxValue,
927+ (double) gdkColor.Blue / ushort.MaxValue,
928+ 1.0);
929+ cr.Color = (MItem.Disabled ? highlightColor.SetSaturation (0).SetAlpha (0.4) :
930+ highlightColor.SetAlpha (Selected ? 1.0 : 0.95));
931+ cr.RoundedRectangle (x + 2, y + 2, px - allocation.X - offset, Size / 2.0 - 4, 3.0);
932+ cr.Fill ();
933+ }
934+
935+ //update the areas
936+ SliderArea = new Gdk.Rectangle (offset, 0, (int)sliderwidth, MenuHeight);
937+ DragArea = new Gdk.Rectangle ((int)px - allocation.X, (int)py - allocation.Y, Size, Size);
938+
939+ (cr.Target as IDisposable).Dispose ();
940+ }
941+
942+ return true;
943+ }
944+
945+ public override void Dispose ()
946+ {
947+ (MItem as SliderMenuItem).PositionChanged -= ItemPositionChanged;
948+
949+ base.Dispose ();
950+ }
951+ }
952+}
953
954=== added file 'Docky/Docky/Menus/TextMenuItemWidget.cs'
955--- Docky/Docky/Menus/TextMenuItemWidget.cs 1970-01-01 00:00:00 +0000
956+++ Docky/Docky/Menus/TextMenuItemWidget.cs 2012-02-04 07:53:22 +0000
957@@ -0,0 +1,121 @@
958+//
959+// Copyright (C) 2009 Jason Smith, Robert Dyer
960+// Copyright (C) 2010 Robert Dyer
961+// Copyright (C) 2011 Rico Tzschichholz
962+//
963+// This program is free software: you can redistribute it and/or modify
964+// it under the terms of the GNU General Public License as published by
965+// the Free Software Foundation, either version 3 of the License, or
966+// (at your option) any later version.
967+//
968+// This program is distributed in the hope that it will be useful,
969+// but WITHOUT ANY WARRANTY; without even the implied warranty of
970+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
971+// GNU General Public License for more details.
972+//
973+// You should have received a copy of the GNU General Public License
974+// along with this program. If not, see <http://www.gnu.org/licenses/>.
975+//
976+
977+using System;
978+using System.Collections.Generic;
979+
980+using Cairo;
981+using Gdk;
982+using Gtk;
983+
984+using Docky.Services;
985+using Docky.CairoHelper;
986+
987+namespace Docky.Menus
988+{
989+ internal class TextMenuItemWidget : MenuItemWidget
990+ {
991+ internal TextMenuItemWidget (MenuItem item) : base(item)
992+ {
993+ CalcTextWidth ();
994+ }
995+
996+ protected void CalcTextWidth ()
997+ {
998+ using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
999+ char accel;
1000+
1001+ if (MItem.Mnemonic.HasValue)
1002+ layout.SetMarkupWithAccel (MItem.Text, '_', out accel);
1003+ else
1004+ layout.SetMarkup (MItem.Text);
1005+ layout.FontDescription = Style.FontDescription;
1006+ layout.Ellipsize = Pango.EllipsizeMode.End;
1007+ layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
1008+ layout.FontDescription.Weight = Pango.Weight.Bold;
1009+
1010+ Pango.Rectangle logical, ink;
1011+ layout.GetPixelExtents (out ink, out logical);
1012+
1013+ TextWidth = Math.Min (MaxWidth, Math.Max (MinWidth, logical.Width));
1014+ HasTooltip = TextWidth < logical.Width;
1015+
1016+ layout.Context.Dispose ();
1017+ }
1018+
1019+ SetSize ();
1020+ }
1021+
1022+ protected override void ItemTextChanged (object sender, EventArgs e)
1023+ {
1024+ CalcTextWidth ();
1025+ QueueDraw ();
1026+ }
1027+
1028+ protected override bool OnExposeEvent (EventExpose evnt)
1029+ {
1030+ if (!IsRealized)
1031+ return false;
1032+
1033+ Gdk.Rectangle allocation = Allocation;
1034+
1035+ using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
1036+ if (Selected && !MItem.Disabled) {
1037+ cr.Rectangle (allocation.X, allocation.Y, allocation.Width, allocation.Height);
1038+ cr.Color = TextColor.SetAlpha (.1);
1039+ cr.Fill ();
1040+ }
1041+
1042+ if (MItem.ShowIcons)
1043+ DrawMenuItemIcon (cr);
1044+
1045+ using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
1046+ char accel;
1047+ if (MItem.Mnemonic.HasValue)
1048+ layout.SetMarkupWithAccel (MItem.Text, '_', out accel);
1049+ else
1050+ layout.SetMarkup (MItem.Text);
1051+ layout.Width = Pango.Units.FromPixels (TextWidth);
1052+ layout.FontDescription = Style.FontDescription;
1053+ layout.Ellipsize = Pango.EllipsizeMode.End;
1054+ layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
1055+ layout.FontDescription.Weight = Pango.Weight.Bold;
1056+
1057+ Pango.Rectangle logical, ink;
1058+ layout.GetPixelExtents (out ink, out logical);
1059+
1060+ int offset = Padding;
1061+ if (MenuShowingIcons)
1062+ offset += MenuHeight + Padding;
1063+
1064+ cr.MoveTo (allocation.X + offset, allocation.Y + (allocation.Height - logical.Height) / 2);
1065+ Pango.CairoHelper.LayoutPath (cr, layout);
1066+ cr.Color = TextColor.SetAlpha (MItem.Disabled ? 0.5 : 1);
1067+ cr.Fill ();
1068+
1069+ layout.Context.Dispose ();
1070+ }
1071+
1072+ (cr.Target as IDisposable).Dispose ();
1073+ }
1074+
1075+ return true;
1076+ }
1077+ }
1078+}
1079
1080=== modified file 'Docky/Makefile.am'
1081--- Docky/Makefile.am 2010-12-01 21:43:00 +0000
1082+++ Docky/Makefile.am 2012-02-04 07:53:22 +0000
1083@@ -41,6 +41,8 @@
1084 Docky/Menus/DockMenu.cs \
1085 Docky/Menus/MenuItemWidget.cs \
1086 Docky/Menus/SeparatorWidget.cs \
1087+ Docky/Menus/SliderMenuItemWidget.cs \
1088+ Docky/Menus/TextMenuItemWidget.cs \
1089 Docky/Menus/DockItemMenu.cs
1090
1091 RESOURCES = \
1092
1093=== modified file 'Docky/gtk-gui/gui.stetic'
1094--- Docky/gtk-gui/gui.stetic 2011-12-22 15:47:35 +0000
1095+++ Docky/gtk-gui/gui.stetic 2012-02-04 07:53:22 +0000
1096@@ -5,12 +5,9 @@
1097 <target-gtk-version>2.12</target-gtk-version>
1098 </configuration>
1099 <import>
1100+ <widget-library name="notify-sharp, Version=0.4.0.0, Culture=neutral, PublicKeyToken=2df29c54e245917a" />
1101 <widget-library name="wnck-sharp, Version=2.20.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
1102 <widget-library name="gnomedesktop-sharp, Version=2.20.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
1103- <widget-library name="../../Docky.Services/bin/Debug/Docky.Services.dll" />
1104- <widget-library name="../../Docky.Items/bin/Debug/Docky.Items.dll" />
1105- <widget-library name="../../Docky.DBus/bin/Debug/Docky.DBus.dll" />
1106- <widget-library name="../../Docky.Widgets/bin/Debug/Docky.Widgets.dll" />
1107 <widget-library name="../bin/Debug/Docky.exe" internal="true" />
1108 </import>
1109 <widget class="Gtk.Bin" id="Docky.Interface.DockPreferences" design-size="500 356">
1110
1111=== modified file 'StandardPlugins/Makefile.am'
1112--- StandardPlugins/Makefile.am 2011-02-23 20:17:57 +0000
1113+++ StandardPlugins/Makefile.am 2012-02-04 07:53:22 +0000
1114@@ -12,6 +12,7 @@
1115 NPR \
1116 RecentDocuments \
1117 SessionManager \
1118+ Sound \
1119 Timer \
1120 Trash \
1121 Weather \
1122@@ -31,6 +32,7 @@
1123 NPR \
1124 RecentDocuments \
1125 SessionManager \
1126+ Sound \
1127 Timer \
1128 Trash \
1129 Weather \
1130
1131=== added directory 'StandardPlugins/Sound'
1132=== added file 'StandardPlugins/Sound/Makefile.am'
1133--- StandardPlugins/Sound/Makefile.am 1970-01-01 00:00:00 +0000
1134+++ StandardPlugins/Sound/Makefile.am 2012-02-04 07:53:22 +0000
1135@@ -0,0 +1,45 @@
1136+# Simple component buildsystem
1137+include $(top_srcdir)/build.rules.docklets.mk
1138+
1139+ASSEMBLY = Sound
1140+
1141+FILES = \
1142+ src/SoundDockItem.cs \
1143+ src/SoundItemProvider.cs \
1144+ src/PulseAudio-Sharp/Channel.cs \
1145+ src/PulseAudio-Sharp/Client.cs \
1146+ src/PulseAudio-Sharp/Context.cs \
1147+ src/PulseAudio-Sharp/GLib.cs \
1148+ src/PulseAudio-Sharp/MainLoop.cs \
1149+ src/PulseAudio-Sharp/Operation.cs \
1150+ src/PulseAudio-Sharp/PropList.cs \
1151+ src/PulseAudio-Sharp/Sample.cs \
1152+ src/PulseAudio-Sharp/ServerInfo.cs \
1153+ src/PulseAudio-Sharp/Sink.cs \
1154+ src/PulseAudio-Sharp/SinkInput.cs \
1155+ src/PulseAudio-Sharp/UnmanagedCallbackManager.cs \
1156+ src/PulseAudio-Sharp/Util.cs \
1157+ src/PulseAudio-Sharp/Volume.cs
1158+
1159+RESOURCES = \
1160+ Resources/Sound.addin.xml
1161+
1162+PROJECT_REFERENCES= \
1163+ Docky.CairoHelper \
1164+ Docky.Items \
1165+ Docky.Services
1166+
1167+REFERENCES = \
1168+ System \
1169+ System.Core \
1170+ Mono.Posix \
1171+ $(MONO_CAIRO_LIBS) \
1172+ $(GTK_SHARP_20_LIBS) \
1173+ $(GLIB_SHARP_20_LIBS) \
1174+ $(GIO_SHARP_LIBS) \
1175+ $(WNCK_SHARP_10_LIBS)
1176+
1177+EXTRA_DIST += Sound.dll.config
1178+
1179+plugin_DATA += Sound.dll.config
1180+
1181
1182=== added directory 'StandardPlugins/Sound/Resources'
1183=== added file 'StandardPlugins/Sound/Resources/Sound.addin.xml.in'
1184--- StandardPlugins/Sound/Resources/Sound.addin.xml.in 1970-01-01 00:00:00 +0000
1185+++ StandardPlugins/Sound/Resources/Sound.addin.xml.in 2012-02-04 07:53:22 +0000
1186@@ -0,0 +1,25 @@
1187+<Addin
1188+ id="Sound"
1189+ namespace="Docky"
1190+ version="1.0"
1191+ isroot="false"
1192+ defaultEnabled="false"
1193+ name="Sound"
1194+ description="Control your Pulseaudio volumes"
1195+ author="Rico Tzschichholz"
1196+ icon="preferences-desktop-sound"
1197+>
1198+
1199+ <Dependencies>
1200+ <Addin id="Items" version="1.0" />
1201+ </Dependencies>
1202+
1203+ <Runtime>
1204+ <Import assembly="Sound.dll"/>
1205+ </Runtime>
1206+
1207+ <Extension path="/Docky/ItemProvider">
1208+ <ItemProvider type="Sound.SoundItemProvider" />
1209+ </Extension>
1210+
1211+</Addin>
1212
1213=== added file 'StandardPlugins/Sound/Sound.csproj'
1214--- StandardPlugins/Sound/Sound.csproj 1970-01-01 00:00:00 +0000
1215+++ StandardPlugins/Sound/Sound.csproj 2012-02-04 07:53:22 +0000
1216@@ -0,0 +1,79 @@
1217+<?xml version="1.0" encoding="utf-8"?>
1218+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
1219+ <PropertyGroup>
1220+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
1221+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
1222+ <ProductVersion>9.0.21022</ProductVersion>
1223+ <SchemaVersion>2.0</SchemaVersion>
1224+ <ProjectGuid>{16B2B0E3-3437-4F07-958D-E961E132F5F5}</ProjectGuid>
1225+ <OutputType>Library</OutputType>
1226+ <RootNamespace>Sound</RootNamespace>
1227+ <AssemblyName>Sound</AssemblyName>
1228+ <ReleaseVersion>2.2</ReleaseVersion>
1229+ </PropertyGroup>
1230+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
1231+ <DebugSymbols>true</DebugSymbols>
1232+ <DebugType>full</DebugType>
1233+ <Optimize>false</Optimize>
1234+ <OutputPath>bin\Debug</OutputPath>
1235+ <DefineConstants>DEBUG</DefineConstants>
1236+ <ErrorReport>prompt</ErrorReport>
1237+ <WarningLevel>4</WarningLevel>
1238+ </PropertyGroup>
1239+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
1240+ <DebugType>none</DebugType>
1241+ <Optimize>false</Optimize>
1242+ <OutputPath>bin\Release</OutputPath>
1243+ <ErrorReport>prompt</ErrorReport>
1244+ <WarningLevel>4</WarningLevel>
1245+ </PropertyGroup>
1246+ <ItemGroup>
1247+ <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
1248+ <Package>gtk-sharp-2.0</Package>
1249+ </Reference>
1250+ <Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
1251+ <Package>glib-sharp-2.0</Package>
1252+ </Reference>
1253+ <Reference Include="System" />
1254+ <Reference Include="System.Core" />
1255+ <Reference Include="Mono.Posix" />
1256+ <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
1257+ <Package>gtk-sharp-2.0</Package>
1258+ </Reference>
1259+ </ItemGroup>
1260+ <ItemGroup>
1261+ <Compile Include="src\SoundDockItem.cs" />
1262+ <Compile Include="src\SoundItemProvider.cs" />
1263+ <Compile Include="src\PulseAudio-Sharp\Channel.cs" />
1264+ <Compile Include="src\PulseAudio-Sharp\Context.cs" />
1265+ <Compile Include="src\PulseAudio-Sharp\GLib.cs" />
1266+ <Compile Include="src\PulseAudio-Sharp\MainLoop.cs" />
1267+ <Compile Include="src\PulseAudio-Sharp\Operation.cs" />
1268+ <Compile Include="src\PulseAudio-Sharp\PropList.cs" />
1269+ <Compile Include="src\PulseAudio-Sharp\Sample.cs" />
1270+ <Compile Include="src\PulseAudio-Sharp\ServerInfo.cs" />
1271+ <Compile Include="src\PulseAudio-Sharp\Sink.cs" />
1272+ <Compile Include="src\PulseAudio-Sharp\SinkInput.cs" />
1273+ <Compile Include="src\PulseAudio-Sharp\UnmanagedCallbackManager.cs" />
1274+ <Compile Include="src\PulseAudio-Sharp\Util.cs" />
1275+ <Compile Include="src\PulseAudio-Sharp\Volume.cs" />
1276+ <Compile Include="src\PulseAudio-Sharp\Client.cs" />
1277+ </ItemGroup>
1278+ <ItemGroup>
1279+ <ProjectReference Include="..\..\Docky.Items\Docky.Items.csproj">
1280+ <Project>{AB6E0EDD-6443-4F99-9EAC-DABC906F080D}</Project>
1281+ <Name>Docky.Items</Name>
1282+ </ProjectReference>
1283+ <ProjectReference Include="..\..\Docky.Services\Docky.Services.csproj">
1284+ <Project>{8A6E0EDD-6443-4F99-9EAC-D9CC906F080D}</Project>
1285+ <Name>Docky.Services</Name>
1286+ </ProjectReference>
1287+ </ItemGroup>
1288+ <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
1289+ <ItemGroup>
1290+ <Folder Include="Resources\" />
1291+ </ItemGroup>
1292+ <ItemGroup>
1293+ <None Include="Resources\Sound.addin.xml" />
1294+ </ItemGroup>
1295+</Project>
1296\ No newline at end of file
1297
1298=== added file 'StandardPlugins/Sound/Sound.dll.config'
1299--- StandardPlugins/Sound/Sound.dll.config 1970-01-01 00:00:00 +0000
1300+++ StandardPlugins/Sound/Sound.dll.config 2012-02-04 07:53:22 +0000
1301@@ -0,0 +1,6 @@
1302+<configuration>
1303+ <dllmap dll="pulse-mainloop-glib" target="libpulse-mainloop-glib.so.0" />
1304+ <dllmap dll="pulse" target="libpulse.so.0" />
1305+</configuration>
1306+
1307+
1308
1309=== added directory 'StandardPlugins/Sound/src'
1310=== added directory 'StandardPlugins/Sound/src/PulseAudio-Sharp'
1311=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs'
1312--- StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs 1970-01-01 00:00:00 +0000
1313+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs 2012-02-04 07:53:22 +0000
1314@@ -0,0 +1,133 @@
1315+//
1316+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
1317+//
1318+// Channel.cs is a part of Pulseaudio#
1319+//
1320+// Pulseaudio# is free software: you can redistribute it and/or modify
1321+// it under the terms of the GNU Lesser General Public License as published by
1322+// the Free Software Foundation, either version 3 of the License, or
1323+// (at your option) any later version.
1324+//
1325+// Pulseaudio# is distributed in the hope that it will be useful,
1326+// but WITHOUT ANY WARRANTY; without even the implied warranty of
1327+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1328+// GNU Lesser General Public License for more details.
1329+//
1330+// You should have received a copy of the GNU Lesser General Public License
1331+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
1332+//
1333+
1334+using System;
1335+using System.Runtime.InteropServices;
1336+
1337+namespace Pulseaudio
1338+{
1339+ public enum ChannelPosition
1340+ {
1341+ Invalid = -1,
1342+ Mono = 0,
1343+
1344+ FrontLeft, /* Apple calls this 'Left' */
1345+ FrontRight, /* Apple calls this 'Right' */
1346+ FrontCenter, /* Apple calls this 'Center' */
1347+
1348+ Left = FrontLeft,
1349+ Right = FrontRight,
1350+ Center = FrontCenter,
1351+
1352+ RearCentre, /* Microsoft calls this 'Back Center', Apple calls this 'Center Surround' */
1353+ RearLeft, /* Microsoft calls this 'Back Left', Apple calls this 'Left Surround' */
1354+ RearRight, /* Microsoft calls this 'Back Right', Apple calls this 'Right Surround' */
1355+
1356+ LFE, /* Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */
1357+ SubWoofer = LFE,
1358+
1359+ FrontLeftOfCenter, /* Apple calls this 'Left Center' */
1360+ FrontRightOfCenter, /* Apple calls this 'Right Center */
1361+
1362+ SideLeft, /* Apple calls this 'Left Surround Direct' */
1363+ SideRight, /* Apple calls this 'Right Surround Direct' */
1364+
1365+ PA_CHANNEL_POSITION_AUX0,
1366+ PA_CHANNEL_POSITION_AUX1,
1367+ PA_CHANNEL_POSITION_AUX2,
1368+ PA_CHANNEL_POSITION_AUX3,
1369+ PA_CHANNEL_POSITION_AUX4,
1370+ PA_CHANNEL_POSITION_AUX5,
1371+ PA_CHANNEL_POSITION_AUX6,
1372+ PA_CHANNEL_POSITION_AUX7,
1373+ PA_CHANNEL_POSITION_AUX8,
1374+ PA_CHANNEL_POSITION_AUX9,
1375+ PA_CHANNEL_POSITION_AUX10,
1376+ PA_CHANNEL_POSITION_AUX11,
1377+ PA_CHANNEL_POSITION_AUX12,
1378+ PA_CHANNEL_POSITION_AUX13,
1379+ PA_CHANNEL_POSITION_AUX14,
1380+ PA_CHANNEL_POSITION_AUX15,
1381+ PA_CHANNEL_POSITION_AUX16,
1382+ PA_CHANNEL_POSITION_AUX17,
1383+ PA_CHANNEL_POSITION_AUX18,
1384+ PA_CHANNEL_POSITION_AUX19,
1385+ PA_CHANNEL_POSITION_AUX20,
1386+ PA_CHANNEL_POSITION_AUX21,
1387+ PA_CHANNEL_POSITION_AUX22,
1388+ PA_CHANNEL_POSITION_AUX23,
1389+ PA_CHANNEL_POSITION_AUX24,
1390+ PA_CHANNEL_POSITION_AUX25,
1391+ PA_CHANNEL_POSITION_AUX26,
1392+ PA_CHANNEL_POSITION_AUX27,
1393+ PA_CHANNEL_POSITION_AUX28,
1394+ PA_CHANNEL_POSITION_AUX29,
1395+ PA_CHANNEL_POSITION_AUX30,
1396+ PA_CHANNEL_POSITION_AUX31,
1397+
1398+ TopCenter, /* Apple calls this 'Top Center Surround' */
1399+
1400+ TopFrontLeft, /* Apple calls this 'Vertical Height Left' */
1401+ TopFrontRight, /* Apple calls this 'Vertical Height Right' */
1402+ TopFrontCenter, /* Apple calls this 'Vertical Height Center' */
1403+
1404+ TopRearLeft, /* Microsoft and Apple call this 'Top Back Left' */
1405+ TopRearRight, /* Microsoft and Apple call this 'Top Back Right' */
1406+ TopRearCenter, /* Microsoft and Apple call this 'Top Back Center' */
1407+
1408+ PA_CHANNEL_POSITION_MAX
1409+ }
1410+
1411+ [StructLayout (LayoutKind.Sequential)]
1412+ public struct ChannelMap
1413+ {
1414+ public byte channels;
1415+ [MarshalAs (UnmanagedType.ByValArray, SizeConst=Constants.MaxChannels)]
1416+ public ChannelPosition [] map;
1417+
1418+ private void Init ()
1419+ {
1420+ map = new ChannelPosition[Constants.MaxChannels];
1421+ pa_channel_map_init (ref this);
1422+ }
1423+
1424+ public static ChannelMap StereoMapping ()
1425+ {
1426+ ChannelMap mapping = new ChannelMap ();
1427+ mapping.Init ();
1428+ pa_channel_map_init_stereo (ref mapping);
1429+ return mapping;
1430+ }
1431+
1432+ public static ChannelMap MonoMapping ()
1433+ {
1434+ ChannelMap mapping = new ChannelMap ();
1435+ mapping.Init ();
1436+ pa_channel_map_init_mono (ref mapping);
1437+ return mapping;
1438+ }
1439+
1440+ [DllImport ("pulse")]
1441+ private static extern void pa_channel_map_init (ref ChannelMap map);
1442+ [DllImport ("pulse")]
1443+ private static extern void pa_channel_map_init_stereo (ref ChannelMap map);
1444+ [DllImport ("pulse")]
1445+ private static extern void pa_channel_map_init_mono (ref ChannelMap map);
1446+ }
1447+}
1448
1449=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs'
1450--- StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs 1970-01-01 00:00:00 +0000
1451+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs 2012-02-04 07:53:22 +0000
1452@@ -0,0 +1,118 @@
1453+//
1454+// Copyright © 2010 Rico Tzschichholz
1455+//
1456+// Client.cs is a part of Pulseaudio#
1457+//
1458+// Pulseaudio# is free software: you can redistribute it and/or modify
1459+// it under the terms of the GNU Lesser General Public License as published by
1460+// the Free Software Foundation, either version 3 of the License, or
1461+// (at your option) any later version.
1462+//
1463+// Pulseaudio# is distributed in the hope that it will be useful,
1464+// but WITHOUT ANY WARRANTY; without even the implied warranty of
1465+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1466+// GNU Lesser General Public License for more details.
1467+//
1468+// You should have received a copy of the GNU Lesser General Public License
1469+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
1470+//
1471+
1472+using System;
1473+using System.Runtime.InteropServices;
1474+
1475+namespace Pulseaudio
1476+{
1477+ [StructLayout (LayoutKind.Sequential)]
1478+ public class ClientInfo
1479+ {
1480+ public UInt32 index; /**< Index of the client */
1481+ IntPtr name; /**< Name of the client */
1482+ public UInt32 owner_module; /**< Index of the owning module of this client, or PA_INVALID_INDEX */
1483+ IntPtr driver; /**< Driver name. */
1484+ IntPtr proplist; /**< Property list \since 0.9.11 */
1485+
1486+ public string Name {
1487+ get {
1488+ return Marshal.PtrToStringAnsi (name);
1489+ }
1490+ }
1491+
1492+ public string Driver {
1493+ get {
1494+ return Marshal.PtrToStringAnsi (driver);
1495+ }
1496+ }
1497+
1498+ public PropList Properties {
1499+ get {
1500+ return new PropList (proplist);
1501+ }
1502+ }
1503+
1504+ public ClientInfo()
1505+ {
1506+ }
1507+ }
1508+
1509+ public class Client : IDisposable {
1510+ private void UpdateFromInfo (ClientInfo i)
1511+ {
1512+ info = i;
1513+ Name = i.Name;
1514+ Driver = i.Driver;
1515+ Properties = i.Properties.Copy ();
1516+ }
1517+
1518+ private bool disposed = false;
1519+ private ClientInfo info;
1520+ private Context context;
1521+
1522+ public Client (Context c, ClientInfo info)
1523+ {
1524+ context = c;
1525+ this.info = info;
1526+ Name = info.Name;
1527+ Driver = info.Driver;
1528+ Properties = info.Properties.Copy ();
1529+ }
1530+
1531+ public void Dispose ()
1532+ {
1533+ Dispose (true);
1534+ GC.SuppressFinalize (this);
1535+ }
1536+
1537+ protected virtual void Dispose (bool explicitlyCalled)
1538+ {
1539+ if (!disposed) {
1540+ if (explicitlyCalled) {
1541+ }
1542+ disposed = true;
1543+ }
1544+ }
1545+
1546+ public string Name {
1547+ get; private set;
1548+ }
1549+
1550+ public string Driver {
1551+ get; private set;
1552+ }
1553+
1554+ public PropList Properties {
1555+ get; private set;
1556+ }
1557+
1558+ public UInt32 Index {
1559+ get {
1560+ return info.index;
1561+ }
1562+ }
1563+
1564+ public UInt32 OwnerModule {
1565+ get {
1566+ return info.owner_module;
1567+ }
1568+ }
1569+ }
1570+}
1571
1572=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs'
1573--- StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs 1970-01-01 00:00:00 +0000
1574+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs 2012-02-04 07:53:22 +0000
1575@@ -0,0 +1,655 @@
1576+//
1577+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
1578+//
1579+// Context.cs is a part of Pulseaudio#
1580+//
1581+// Pulseaudio# is free software: you can redistribute it and/or modify
1582+// it under the terms of the GNU Lesser General Public License as published by
1583+// the Free Software Foundation, either version 3 of the License, or
1584+// (at your option) any later version.
1585+//
1586+// Pulseaudio# is distributed in the hope that it will be useful,
1587+// but WITHOUT ANY WARRANTY; without even the implied warranty of
1588+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1589+// GNU Lesser General Public License for more details.
1590+//
1591+// You should have received a copy of the GNU Lesser General Public License
1592+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
1593+//
1594+
1595+using System;
1596+using System.Runtime.InteropServices;
1597+using System.Collections.Generic;
1598+using System.Threading;
1599+using System.Reflection;
1600+
1601+namespace Pulseaudio
1602+{
1603+ public enum EventType {
1604+ Added,
1605+ Changed,
1606+ Removed,
1607+ Error
1608+ }
1609+
1610+ public class ServerEventArgs : EventArgs
1611+ {
1612+ public ServerEventArgs (EventType t, UInt32 index)
1613+ {
1614+ this.Type = t;
1615+ this.index = index;
1616+ }
1617+ public EventType Type { get; private set; }
1618+ public UInt32 index { get; private set; }
1619+ }
1620+
1621+ public class Context : IDisposable
1622+ {
1623+ private bool disposed = false;
1624+ private HandleRef context;
1625+ private MainLoop loop;
1626+ private UnmanagedCallbackManager cbManager;
1627+ private ContextNotifyCB notifyCB;
1628+ private SubscriptionEventCB eventCB;
1629+
1630+ public delegate void ConnectionStateHandler ();
1631+
1632+ public event ConnectionStateHandler Ready;
1633+ public event ConnectionStateHandler Connecting;
1634+
1635+ public Context ()
1636+ {
1637+ Init (Assembly.GetCallingAssembly ().GetName ().Name);
1638+ }
1639+
1640+ public Context (string clientName)
1641+ {
1642+ Init (clientName);
1643+ }
1644+
1645+ public void Dispose ()
1646+ {
1647+ Dispose (true);
1648+ GC.SuppressFinalize (this);
1649+ }
1650+
1651+ protected virtual void Dispose (bool explicitlyCalled)
1652+ {
1653+ if (!disposed) {
1654+ pa_context_set_state_callback (context, null, IntPtr.Zero);
1655+ pa_context_set_subscribe_callback (context, null, IntPtr.Zero);
1656+ _sinkEventHandler = null;
1657+ _sinkInputEventHandler = null;
1658+ _clientEventHandler = null;
1659+ pa_context_disconnect (context);
1660+ pa_context_unref (context);
1661+ if (explicitlyCalled) {
1662+ loop.Dispose ();
1663+ } else {
1664+ GC.ReRegisterForFinalize (loop);
1665+ }
1666+ disposed = true;
1667+ }
1668+ }
1669+
1670+ ~Context ()
1671+ {
1672+ Dispose (false);
1673+ }
1674+
1675+ private void Init (string clientName)
1676+ {
1677+ cbManager = new UnmanagedCallbackManager ();
1678+ loop = new GLibMainLoop ();
1679+ GC.SuppressFinalize (loop);
1680+ context = new HandleRef (this, pa_context_new (loop.GetAPI (), clientName));
1681+ notifyCB = ContextNotifyHandler;
1682+ pa_context_set_state_callback (context, notifyCB, new IntPtr (0));
1683+ eventCB = SubscriptionEventHandler;
1684+ pa_context_set_subscribe_callback (context, eventCB, IntPtr.Zero);
1685+ }
1686+
1687+ public void Connect ()
1688+ {
1689+ pa_context_connect (context, null, ContextConnectionFlags.None, new IntPtr (0));
1690+ }
1691+
1692+ public UInt32 ServerAPI {
1693+ get {
1694+ return pa_context_get_server_protocol_version (context);
1695+ }
1696+ }
1697+
1698+ public string Version {
1699+ get {
1700+ return "pulseaudio 0.9.16-test2";
1701+ }
1702+ }
1703+
1704+ public ConnectionState State {
1705+ get {
1706+ return pa_context_get_state (context);
1707+ }
1708+ }
1709+
1710+ [Flags]
1711+ public enum ContextConnectionFlags
1712+ {
1713+ None = 0,
1714+ NoAutoSpawn = 1,
1715+ NoFail= 2
1716+ }
1717+
1718+ private delegate void ContextNotifyCB (IntPtr context, IntPtr userdata);
1719+
1720+ private void ContextNotifyHandler (IntPtr unused, IntPtr userdata)
1721+ {
1722+ switch (pa_context_get_state (context)) {
1723+ case ConnectionState.Ready:
1724+ if (Ready != null) {
1725+ Ready ();
1726+ }
1727+ break;
1728+ case ConnectionState.Connecting:
1729+ if (Connecting != null) {
1730+ Connecting ();
1731+ }
1732+ break;
1733+ }
1734+ }
1735+
1736+ public enum ConnectionState {
1737+ Unconnected, /**< The context hasn't been connected yet */
1738+ Connecting, /**< A connection is being established */
1739+ Authorising, /**< The client is authorizing itself to the daemon */
1740+ SettingName, /**< The client is passing its application name to the daemon */
1741+ Ready, /**< The connection is established, the context is ready to execute operations */
1742+ Failed, /**< The connection failed or was disconnected */
1743+ Terminated /**< The connection was terminated cleanly */
1744+ }
1745+
1746+
1747+ public Error LastError {
1748+ get {
1749+ return new Error (pa_context_errno (context));
1750+ }
1751+ }
1752+
1753+ [DllImport ("pulse")]
1754+ private static extern Error.Code pa_context_errno (HandleRef context);
1755+
1756+ public Operation EnumerateSinkInputs (SinkInputCallback cb)
1757+ {
1758+ int cookie = cbManager.NewCookie ();
1759+ Action<IntPtr, NativeSinkInputInfo, int, IntPtr> wrapped_cb = (IntPtr c, NativeSinkInputInfo info, int eol, IntPtr userdata) =>
1760+ {
1761+ if (eol == 0) {
1762+ cb (new SinkInput (info, this), eol);
1763+ } else {
1764+ cbManager.RemoveDelegate (cookie);
1765+ }
1766+ };
1767+ cbManager.AddDelegate (wrapped_cb, cookie);
1768+ return new Operation (pa_context_get_sink_input_info_list (context, wrapped_cb, IntPtr.Zero));
1769+ }
1770+
1771+ public delegate void SinkInputCallback (SinkInput info, int eol);
1772+ private delegate void pa_sink_input_info_cb (IntPtr context, NativeSinkInputInfo info, int eol, IntPtr userdata);
1773+
1774+ internal Operation EnumerateSinks (SinkInfoCallback cb)
1775+ {
1776+ int cookie = cbManager.NewCookie ();
1777+ Action<IntPtr, SinkInfo, int, IntPtr> wrapped_cb = (IntPtr c, SinkInfo info, int eol, IntPtr userdata) => {
1778+ cb (info, eol);
1779+ if (eol != 0) {
1780+ cbManager.RemoveDelegate (cookie);
1781+ }
1782+ };
1783+ cbManager.AddDelegate (wrapped_cb, cookie);
1784+ try {
1785+ return new Operation (pa_context_get_sink_info_list (context, wrapped_cb, IntPtr.Zero));
1786+ } catch (ArgumentNullException) {
1787+ throw new Exception (String.Format ("Error enumerating sinks: {0}", LastError.Message));
1788+ }
1789+ }
1790+
1791+ internal delegate void SinkInfoCallback (SinkInfo info, int eol);
1792+ private delegate void pa_sink_info_cb (IntPtr context, SinkInfo info, int eol, IntPtr userdata);
1793+
1794+ public Operation EnumerateSinks (SinkCallback cb)
1795+ {
1796+ return EnumerateSinks ((SinkInfo info, int eol) => {
1797+ if (eol == 0) {
1798+ cb (new Sink (this, info));
1799+ }
1800+ });
1801+ }
1802+
1803+ public delegate void SinkCallback (Sink s);
1804+
1805+ internal Operation GetSinkInfoByIndex (UInt32 index, SinkInfoCallback cb)
1806+ {
1807+ int cookie = cbManager.NewCookie ();
1808+ Action<IntPtr, SinkInfo, int, IntPtr> wrapped_cb = (IntPtr c, SinkInfo info, int eol, IntPtr userdata) => {
1809+ cb (info, eol);
1810+ };
1811+ cbManager.AddDelegate (wrapped_cb, cookie);
1812+ try {
1813+ return new Operation (pa_context_get_sink_info_by_index (context, index, wrapped_cb, IntPtr.Zero));
1814+ } catch (ArgumentNullException) {
1815+ throw new Exception (String.Format ("Error getting SinkInfo for index {0}: {1}", index, LastError.Message));
1816+ }
1817+ }
1818+
1819+ internal Operation GetSinkInfoByIndex (UInt32 index, Action<IntPtr, SinkInfo, int, IntPtr> cb)
1820+ {
1821+ try {
1822+ return new Operation (pa_context_get_sink_info_by_index (context, index, cb, IntPtr.Zero));
1823+ } catch (ArgumentNullException) {
1824+ throw new Exception (String.Format ("Error getting SinkInfo for index {0}: {1}", index, LastError.Message));
1825+ }
1826+ }
1827+
1828+ [DllImport ("pulse")]
1829+ private static extern IntPtr pa_context_get_sink_info_by_index (HandleRef context,
1830+ UInt32 index,
1831+ Action<IntPtr, SinkInfo, int, IntPtr> cb,
1832+ IntPtr userdata);
1833+
1834+ internal Operation GetSinkInputInfoByIndex (UInt32 index, NativeSinkInputInfoCallback cb)
1835+ {
1836+ int cookie = cbManager.NewCookie ();
1837+ Action<IntPtr, NativeSinkInputInfo, int, IntPtr> wrapped_cb = (IntPtr c, NativeSinkInputInfo info, int eol, IntPtr userdata) => {
1838+ cb (info, eol);
1839+ };
1840+ cbManager.AddDelegate (wrapped_cb, cookie);
1841+ try {
1842+ return new Operation (pa_context_get_sink_input_info (context, index, wrapped_cb, IntPtr.Zero));
1843+ } catch (ArgumentNullException) {
1844+ throw new Exception (String.Format ("Error getting SinkInputInfo for index {0}: {1}", index, LastError.Message));
1845+ }
1846+ }
1847+
1848+ internal delegate void NativeSinkInputInfoCallback (NativeSinkInputInfo info, int eol);
1849+ [DllImport ("pulse")]
1850+ private static extern IntPtr pa_context_get_sink_input_info (HandleRef context,
1851+ UInt32 index,
1852+ Action<IntPtr, NativeSinkInputInfo, int, IntPtr> cb,
1853+ IntPtr userdata);
1854+
1855+ public Operation SetSinkVolume (UInt32 index, Volume vol, OperationSuccessCallback cb)
1856+ {
1857+ int cookie = cbManager.NewCookie ();
1858+ Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
1859+ cb (success);
1860+ };
1861+ cbManager.AddDelegate (wrapped_cb, cookie);
1862+ try {
1863+ return new Operation (pa_context_set_sink_volume_by_index (context,
1864+ index,
1865+ vol,
1866+ wrapped_cb,
1867+ IntPtr.Zero));
1868+ } catch (ArgumentNullException e) {
1869+ throw new Exception (String.Format ("Error setting sink volume: {0}", LastError.Message));
1870+ }
1871+ }
1872+ [DllImport("pulse")]
1873+ private static extern IntPtr pa_context_set_sink_volume_by_index(HandleRef context,
1874+ UInt32 idx,
1875+ Volume vol,
1876+ Action<IntPtr, int, IntPtr> cb,
1877+ IntPtr userdata);
1878+
1879+ public Operation SetSinkInputVolume (UInt32 index, Volume vol, OperationSuccessCallback cb)
1880+ {
1881+ int cookie = cbManager.NewCookie ();
1882+ Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
1883+ cb (success);
1884+ cbManager.RemoveDelegate (cookie);
1885+ };
1886+ cbManager.AddDelegate (wrapped_cb, cookie);
1887+ try {
1888+ return new Operation (pa_context_set_sink_input_volume (context,
1889+ index,
1890+ vol,
1891+ wrapped_cb,
1892+ IntPtr.Zero));
1893+ } catch (ArgumentNullException) {
1894+ throw new Exception (String.Format ("Error setting sink input volume: {0}", LastError.Message));
1895+ }
1896+ }
1897+ [DllImport ("pulse")]
1898+ private static extern IntPtr pa_context_set_sink_input_volume (HandleRef context, UInt32 index, Volume vol, Action<IntPtr, int, IntPtr> cb, IntPtr userdata);
1899+
1900+
1901+ public Operation MoveSinkInputByIndex (UInt32 sinkInputIndex, UInt32 sinkIndex, OperationSuccessCallback cb)
1902+ {
1903+ int cookie = cbManager.NewCookie ();
1904+ Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
1905+ cb (success);
1906+ cbManager.RemoveDelegate (cookie);
1907+ };
1908+ cbManager.AddDelegate (wrapped_cb, cookie);
1909+ try {
1910+ return new Operation (pa_context_move_sink_input_by_index (context,
1911+ sinkInputIndex,
1912+ sinkIndex,
1913+ wrapped_cb,
1914+ IntPtr.Zero));
1915+ } catch (ArgumentNullException) {
1916+ throw new Exception (String.Format ("Error moving SinkInput {0} to sink {1}: {2}", sinkInputIndex, sinkIndex, LastError.Message));
1917+ }
1918+ }
1919+ [DllImport ("pulse")]
1920+ private static extern IntPtr pa_context_move_sink_input_by_index (HandleRef context, UInt32 sinkInputIndex, UInt32 sinkIndex, Action<IntPtr, int, IntPtr> cb, IntPtr userdata);
1921+
1922+
1923+ public Operation SetSinkMute (UInt32 index, bool mute, OperationSuccessCallback cb)
1924+ {
1925+ int cookie = cbManager.NewCookie ();
1926+ Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
1927+ cb (success);
1928+ };
1929+ cbManager.AddDelegate (wrapped_cb, cookie);
1930+ try {
1931+ return new Operation (pa_context_set_sink_mute_by_index (context,
1932+ index,
1933+ mute ? 1 : 0,
1934+ wrapped_cb,
1935+ IntPtr.Zero));
1936+ } catch (ArgumentNullException e) {
1937+ throw new Exception (String.Format ("Error setting sink mute: {0}", LastError.Message));
1938+ }
1939+ }
1940+ [DllImport("pulse")]
1941+ private static extern IntPtr pa_context_set_sink_mute_by_index (HandleRef context,
1942+ UInt32 idx,
1943+ int mute,
1944+ Action<IntPtr, int, IntPtr> cb,
1945+ IntPtr userdata);
1946+
1947+
1948+ public Operation EnumerateClients (ClientCallback cb)
1949+ {
1950+ return EnumerateClients ((ClientInfo info, int eol) => {
1951+ if (eol == 0) {
1952+ cb (new Client (this, info));
1953+ }
1954+ });
1955+ }
1956+
1957+ public delegate void ClientCallback (Client c);
1958+
1959+ internal Operation EnumerateClients (ClientInfoCallback cb)
1960+ {
1961+ int cookie = cbManager.NewCookie ();
1962+ Action<IntPtr, ClientInfo, int, IntPtr> wrapped_cb = (IntPtr c, ClientInfo info, int eol, IntPtr userdata) => {
1963+ cb (info, eol);
1964+ if (eol != 0) {
1965+ cbManager.RemoveDelegate (cookie);
1966+ }
1967+ };
1968+ cbManager.AddDelegate (wrapped_cb, cookie);
1969+ try {
1970+ return new Operation (pa_context_get_client_info_list (context, wrapped_cb, IntPtr.Zero));
1971+ } catch (ArgumentNullException) {
1972+ throw new Exception (String.Format ("Error enumerating clients: {0}", LastError.Message));
1973+ }
1974+ }
1975+
1976+ internal delegate void ClientInfoCallback (ClientInfo info, int eol);
1977+ private delegate void pa_client_info_cb (IntPtr context, ClientInfo info, int eol, IntPtr userdata);
1978+
1979+ internal Operation GetClientInfoByIndex (UInt32 index, ClientInfoCallback cb)
1980+ {
1981+ int cookie = cbManager.NewCookie ();
1982+ Action<IntPtr, ClientInfo, int, IntPtr> wrapped_cb = (IntPtr c, ClientInfo info, int eol, IntPtr userdata) => {
1983+ cb (info, eol);
1984+ };
1985+ cbManager.AddDelegate (wrapped_cb, cookie);
1986+ try {
1987+ return new Operation (pa_context_get_client_info (context, index, wrapped_cb, IntPtr.Zero));
1988+ } catch (ArgumentNullException) {
1989+ throw new Exception (String.Format ("Error getting ClientInfo for index {0}: {1}", index, LastError.Message));
1990+ }
1991+ }
1992+
1993+ internal Operation GetClientInfoByIndex (UInt32 index, Action<IntPtr, ClientInfo, int, IntPtr> cb)
1994+ {
1995+ try {
1996+ return new Operation (pa_context_get_client_info (context, index, cb, IntPtr.Zero));
1997+ } catch (ArgumentNullException) {
1998+ throw new Exception (String.Format ("Error getting ClientInfo for index {0}: {1}", index, LastError.Message));
1999+ }
2000+ }
2001+ [DllImport("pulse")]
2002+ private static extern IntPtr pa_context_get_client_info (HandleRef context,
2003+ UInt32 idx,
2004+ Action<IntPtr, ClientInfo, int, IntPtr> cb,
2005+ IntPtr userdata);
2006+ [DllImport("pulse")]
2007+ private static extern IntPtr pa_context_get_client_info_list (HandleRef context,
2008+ Action<IntPtr, ClientInfo, int, IntPtr> cb,
2009+ IntPtr userdata);
2010+
2011+
2012+ public delegate void OperationSuccessCallback (int success);
2013+ private delegate void pa_context_success_cb (IntPtr context, int success, IntPtr userdata);
2014+
2015+ [DllImport ("pulse")]
2016+ private static extern IntPtr pa_context_new (IntPtr mainloop_api, string appName);
2017+ [DllImport ("pulse")]
2018+ private static extern int pa_context_connect (HandleRef context, string server, ContextConnectionFlags flags,
2019+ IntPtr spawnApi);
2020+ [DllImport ("pulse")]
2021+ private static extern UInt32 pa_context_get_server_protocol_version (HandleRef context);
2022+ [DllImport ("pulse")]
2023+ private static extern void pa_context_set_state_callback (HandleRef context,
2024+ ContextNotifyCB cb,
2025+ IntPtr userdata);
2026+ [DllImport ("pulse")]
2027+ private static extern ConnectionState pa_context_get_state (HandleRef context);
2028+
2029+ [DllImport ("pulse")]
2030+ private static extern IntPtr pa_context_get_sink_input_info_list (HandleRef context,
2031+ Action<IntPtr, NativeSinkInputInfo, int, IntPtr> cb,
2032+ IntPtr userdata);
2033+ [DllImport ("pulse")]
2034+ private static extern IntPtr pa_context_get_sink_info_list (HandleRef context,
2035+ Action<IntPtr, SinkInfo, int, IntPtr> cb,
2036+ IntPtr userdata);
2037+ [DllImport ("pulse")]
2038+ private static extern void pa_context_unref (HandleRef context);
2039+ [DllImport ("pulse")]
2040+ private static extern void pa_context_disconnect (HandleRef context);
2041+
2042+ public delegate void ServerInfoCallback (ServerInfo info);
2043+ private delegate void pa_server_info_cb (IntPtr context, NativeServerInfo info, IntPtr userdata);
2044+ [DllImport ("pulse")]
2045+ private static extern IntPtr pa_context_get_server_info (HandleRef context,
2046+ Action<IntPtr, NativeServerInfo, IntPtr> cb,
2047+ IntPtr userdata);
2048+ public Operation GetServerInfo (ServerInfoCallback cb)
2049+ {
2050+ int cookie = cbManager.NewCookie ();
2051+ Action<IntPtr, NativeServerInfo, IntPtr> wrappedCallback = (_, info, __) => {
2052+ cb (new ServerInfo (info));
2053+ cbManager.RemoveDelegate (cookie);
2054+ };
2055+ cbManager.AddDelegate (wrappedCallback, cookie);
2056+ Operation opn;
2057+ try {
2058+ opn = new Operation (pa_context_get_server_info (context, wrappedCallback, IntPtr.Zero));
2059+ } catch (ArgumentNullException) {
2060+ throw new Exception (String.Format ("Error getting server info: {0}", LastError.Message));
2061+ }
2062+ return opn;
2063+ }
2064+
2065+
2066+ private readonly object eventHandlerLock = new object ();
2067+ private EventHandler<ServerEventArgs> _sinkEventHandler;
2068+ public event EventHandler<ServerEventArgs> SinkEvent {
2069+ add {
2070+ lock (eventHandlerLock) {
2071+ if (_sinkEventHandler == null) {
2072+ UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK);
2073+ }
2074+ _sinkEventHandler += value;
2075+ }
2076+ }
2077+ remove {
2078+ lock (eventHandlerLock) {
2079+ _sinkEventHandler -= value;
2080+ if (_sinkEventHandler == null) {
2081+ UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK);
2082+ }
2083+ }
2084+ }
2085+ }
2086+
2087+ private EventHandler<ServerEventArgs> _sinkInputEventHandler;
2088+ public event EventHandler<ServerEventArgs> SinkInputEvent {
2089+ add {
2090+ lock (eventHandlerLock) {
2091+ if (_sinkInputEventHandler == null) {
2092+ UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK_INPUT);
2093+ }
2094+ _sinkInputEventHandler += value;
2095+ }
2096+ }
2097+ remove {
2098+ lock (eventHandlerLock) {
2099+ _sinkInputEventHandler -= value;
2100+ if (_sinkInputEventHandler == null) {
2101+ UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK_INPUT);
2102+ }
2103+ }
2104+ }
2105+ }
2106+
2107+ private EventHandler<ServerEventArgs> _clientEventHandler;
2108+ public event EventHandler<ServerEventArgs> ClientEvent {
2109+ add {
2110+ lock (eventHandlerLock) {
2111+ if (_clientEventHandler == null) {
2112+ UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_CLIENT);
2113+ }
2114+ _clientEventHandler += value;
2115+ }
2116+ }
2117+ remove {
2118+ lock (eventHandlerLock) {
2119+ _clientEventHandler -= value;
2120+ if (_clientEventHandler == null) {
2121+ UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_CLIENT);
2122+ }
2123+ }
2124+ }
2125+ }
2126+
2127+ private SubscriptionMask registeredEvents = SubscriptionMask.PA_SUBSCRIPTION_MASK_NULL;
2128+ private void UpdateSubscriptions (SubscriptionMask eventType)
2129+ {
2130+ if (registeredEvents != (registeredEvents ^ eventType)) {
2131+ registeredEvents = registeredEvents ^ eventType;
2132+ Operation opn = null;
2133+ try {
2134+ opn = new Operation (pa_context_subscribe (context,
2135+ registeredEvents,
2136+ (_,__,___) => {;},
2137+ IntPtr.Zero));
2138+ } catch (ArgumentNullException) {
2139+ throw new Exception (String.Format ("Error subscribing to server events: {0}", LastError.Message));
2140+ } finally {
2141+ if (opn != null) {
2142+ opn.Dispose ();
2143+ }
2144+ }
2145+ }
2146+ }
2147+
2148+ private void SubscriptionEventHandler (IntPtr context, SubscriptionEventMask e, UInt32 index, IntPtr userdata)
2149+ {
2150+ EventType action = EventType.Error;
2151+ EventHandler<ServerEventArgs> handler = null;
2152+ switch (e & SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
2153+ case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_CHANGE:
2154+ action = EventType.Changed;
2155+ break;
2156+ case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_NEW:
2157+ action = EventType.Added;
2158+ break;
2159+ case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_REMOVE:
2160+ action = EventType.Removed;
2161+ break;
2162+ }
2163+ switch (e & SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
2164+ case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_SINK:
2165+ lock (eventHandlerLock) {
2166+ handler = _sinkEventHandler;
2167+ }
2168+ break;
2169+ case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_SINK_INPUT:
2170+ lock (eventHandlerLock) {
2171+ handler = _sinkInputEventHandler;
2172+ }
2173+ break;
2174+ case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_CLIENT:
2175+ lock (eventHandlerLock) {
2176+ handler = _clientEventHandler;
2177+ }
2178+ break;
2179+ }
2180+ if (handler != null) {
2181+ handler (this, new ServerEventArgs (action, index));
2182+ }
2183+ }
2184+
2185+ private delegate void SubscriptionEventCB (IntPtr context, SubscriptionEventMask e, UInt32 index, IntPtr userdata);
2186+ [DllImport ("pulse")]
2187+ private static extern IntPtr pa_context_subscribe(HandleRef context,
2188+ SubscriptionMask m,
2189+ pa_context_success_cb cb,
2190+ IntPtr userdata);
2191+ [DllImport ("pulse")]
2192+ private static extern void pa_context_set_subscribe_callback (HandleRef context,
2193+ SubscriptionEventCB cb,
2194+ IntPtr userData);
2195+
2196+ [Flags]
2197+ private enum SubscriptionMask : uint {
2198+ PA_SUBSCRIPTION_MASK_NULL = 0x0000U,/**< No events */
2199+ PA_SUBSCRIPTION_MASK_SINK = 0x0001U,/**< Sink events */
2200+ PA_SUBSCRIPTION_MASK_SOURCE = 0x0002U,/**< Source events */
2201+ PA_SUBSCRIPTION_MASK_SINK_INPUT = 0x0004U,/**< Sink input events */
2202+ PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT = 0x0008U,/**< Source output events */
2203+ PA_SUBSCRIPTION_MASK_MODULE = 0x0010U,/**< Module events */
2204+ PA_SUBSCRIPTION_MASK_CLIENT = 0x0020U,/**< Client events */
2205+ PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 0x0040U,/**< Sample cache events */
2206+ PA_SUBSCRIPTION_MASK_SERVER = 0x0080U,/**< Other global server changes. */
2207+ PA_SUBSCRIPTION_MASK_AUTOLOAD = 0x0100U,/**< \deprecated Autoload table events. */
2208+ PA_SUBSCRIPTION_MASK_CARD = 0x0200U,/**< Card events. \since 0.9.15 */
2209+ PA_SUBSCRIPTION_MASK_ALL = 0x02ffU/**< Catch all events */
2210+ }
2211+ [Flags]
2212+ private enum SubscriptionEventMask : uint {
2213+ PA_SUBSCRIPTION_EVENT_SINK = 0x0000U,/**< Event type: Sink */
2214+ PA_SUBSCRIPTION_EVENT_SOURCE = 0x0001U,/**< Event type: Source */
2215+ PA_SUBSCRIPTION_EVENT_SINK_INPUT = 0x0002U,/**< Event type: Sink input */
2216+ PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 0x0003U,/**< Event type: Source output */
2217+ PA_SUBSCRIPTION_EVENT_MODULE = 0x0004U,/**< Event type: Module */
2218+ PA_SUBSCRIPTION_EVENT_CLIENT = 0x0005U,/**< Event type: Client */
2219+ PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 0x0006U,/**< Event type: Sample cache item */
2220+ PA_SUBSCRIPTION_EVENT_SERVER = 0x0007U,/**< Event type: Global server change, only occurring with PA_SUBSCRIPTION_EVENT_CHANGE. */
2221+ PA_SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U,/**< \deprecated Event type: Autoload table changes. */
2222+ PA_SUBSCRIPTION_EVENT_CARD = 0x0009U,/**< Event type: Card \since 0.9.15 */
2223+ PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 0x000FU,/**< A mask to extract the event type from an event value */
2224+ PA_SUBSCRIPTION_EVENT_NEW = 0x0000U,/**< A new object was created */
2225+ PA_SUBSCRIPTION_EVENT_CHANGE = 0x0010U,/**< A property of the object was modified */
2226+ PA_SUBSCRIPTION_EVENT_REMOVE = 0x0020U,/**< An object was removed */
2227+ PA_SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U/**< A mask to extract the event operation from an event value */
2228+ }
2229+ }
2230+}
2231
2232=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs'
2233--- StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs 1970-01-01 00:00:00 +0000
2234+++ StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs 2012-02-04 07:53:22 +0000
2235@@ -0,0 +1,65 @@
2236+//
2237+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
2238+//
2239+// GLib.cs is a part of Pulseaudio#
2240+//
2241+// Pulseaudio# is free software: you can redistribute it and/or modify
2242+// it under the terms of the GNU Lesser General Public License as published by
2243+// the Free Software Foundation, either version 3 of the License, or
2244+// (at your option) any later version.
2245+//
2246+// Pulseaudio# is distributed in the hope that it will be useful,
2247+// but WITHOUT ANY WARRANTY; without even the implied warranty of
2248+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2249+// GNU Lesser General Public License for more details.
2250+//
2251+// You should have received a copy of the GNU Lesser General Public License
2252+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
2253+//
2254+
2255+using System;
2256+using System.Collections.Generic;
2257+using System.Threading;
2258+using Pulseaudio;
2259+using g = GLib;
2260+
2261+namespace Pulseaudio.GLib
2262+{
2263+ public static class GLibExtensions
2264+ {
2265+ public static void Wait (this Operation opn)
2266+ {
2267+ //FIXME this is an ugly hack to workaround the problem that MainContext.Iteration ()
2268+ // seems to iterate not the right mainloop wich results in an endless loop where
2269+ // operation never leaves the Running state
2270+ bool timeout = false;
2271+ g::Timeout.Add (300, delegate {
2272+ timeout = true;
2273+ return false;
2274+ });
2275+ while (!timeout && opn.State == Operation.Status.Running) {
2276+ g::MainContext.Iteration ();
2277+ }
2278+ }
2279+
2280+ public static void ConnectAndWait (this Context context)
2281+ {
2282+ ManualResetEvent ready = new ManualResetEvent (false);
2283+ context.Ready += delegate {
2284+ ready.Set ();
2285+ };
2286+ context.Connect ();
2287+ //FIXME this is an ugly hack to workaround the problem that MainContext.Iteration ()
2288+ // seems to iterate not the right mainloop wich results in an endless loop where
2289+ // operation never leaves the Running state
2290+ bool timeout = false;
2291+ g::Timeout.Add (300, delegate {
2292+ timeout = true;
2293+ return false;
2294+ });
2295+ while (!timeout && !ready.WaitOne (0, true)) {
2296+ while (g::MainContext.Iteration (false)) {};
2297+ }
2298+ }
2299+ }
2300+}
2301
2302=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs'
2303--- StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs 1970-01-01 00:00:00 +0000
2304+++ StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs 2012-02-04 07:53:22 +0000
2305@@ -0,0 +1,76 @@
2306+//
2307+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
2308+//
2309+// MainLoop.cs is a part of Pulseaudio#
2310+//
2311+// Pulseaudio# is free software: you can redistribute it and/or modify
2312+// it under the terms of the GNU Lesser General Public License as published by
2313+// the Free Software Foundation, either version 3 of the License, or
2314+// (at your option) any later version.
2315+//
2316+// Pulseaudio# is distributed in the hope that it will be useful,
2317+// but WITHOUT ANY WARRANTY; without even the implied warranty of
2318+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2319+// GNU Lesser General Public License for more details.
2320+//
2321+// You should have received a copy of the GNU Lesser General Public License
2322+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
2323+//
2324+
2325+using System;
2326+using System.Runtime.InteropServices;
2327+
2328+namespace Pulseaudio
2329+{
2330+ public interface MainLoop : IDisposable
2331+ {
2332+ //TODO: Make this into an actual managed implementation of a Pulseaudio main loop
2333+ //For the moment, just require that we can get a pa_mainloop_api pointer out.
2334+ IntPtr GetAPI ();
2335+ }
2336+
2337+ public class GLibMainLoop : MainLoop
2338+ {
2339+ IntPtr pa_mainloop = IntPtr.Zero;
2340+ bool disposed = false;
2341+
2342+ public GLibMainLoop ()
2343+ {
2344+ pa_mainloop = pa_glib_mainloop_new (IntPtr.Zero);
2345+ }
2346+
2347+ public IntPtr GetAPI ()
2348+ {
2349+ if (disposed) {
2350+ throw new ObjectDisposedException ("GLibMainLoop", "GLibMainLoop used after being disposed!");
2351+ }
2352+ return pa_glib_mainloop_get_api (pa_mainloop);
2353+ }
2354+
2355+ public void Dispose ()
2356+ {
2357+ Dispose (true);
2358+ GC.SuppressFinalize (this);
2359+ }
2360+
2361+ protected virtual void Dispose (bool explicitlyCalled)
2362+ {
2363+ if (!disposed) {
2364+ pa_glib_mainloop_free (pa_mainloop);
2365+ disposed = true;
2366+ }
2367+ }
2368+
2369+ ~GLibMainLoop ()
2370+ {
2371+ Dispose (false);
2372+ }
2373+
2374+ [DllImport ("pulse-mainloop-glib")]
2375+ private static extern IntPtr pa_glib_mainloop_new (IntPtr main_context);
2376+ [DllImport ("pulse-mainloop-glib")]
2377+ private static extern IntPtr pa_glib_mainloop_get_api (IntPtr pa_mainloop);
2378+ [DllImport ("pulse-mainloop-glib")]
2379+ private static extern void pa_glib_mainloop_free (IntPtr pa_mainloop);
2380+ }
2381+}
2382
2383=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs'
2384--- StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs 1970-01-01 00:00:00 +0000
2385+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs 2012-02-04 07:53:22 +0000
2386@@ -0,0 +1,73 @@
2387+//
2388+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
2389+//
2390+// Operation.cs is a part of Pulseaudio#
2391+//
2392+// Pulseaudio# is free software: you can redistribute it and/or modify
2393+// it under the terms of the GNU Lesser General Public License as published by
2394+// the Free Software Foundation, either version 3 of the License, or
2395+// (at your option) any later version.
2396+//
2397+// Pulseaudio# is distributed in the hope that it will be useful,
2398+// but WITHOUT ANY WARRANTY; without even the implied warranty of
2399+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2400+// GNU Lesser General Public License for more details.
2401+//
2402+// You should have received a copy of the GNU Lesser General Public License
2403+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
2404+//
2405+
2406+using System;
2407+using System.Runtime.InteropServices;
2408+
2409+namespace Pulseaudio
2410+{
2411+ public sealed class Operation : IDisposable
2412+ {
2413+ public enum Status
2414+ {
2415+ Running,
2416+ Done,
2417+ Cancelled
2418+ }
2419+
2420+ private HandleRef handle;
2421+ internal Operation(IntPtr handle)
2422+ {
2423+ if (handle == IntPtr.Zero) {
2424+ throw new ArgumentNullException ("handle", "Tried to create an Operation with null pa_operation pointer");
2425+ }
2426+ this.handle = new HandleRef (this, handle);
2427+ }
2428+
2429+ ~Operation ()
2430+ {
2431+ if (handle.Handle != IntPtr.Zero) {
2432+ pa_operation_unref (handle);
2433+ }
2434+ }
2435+
2436+ public void Dispose ()
2437+ {
2438+ pa_operation_unref (handle);
2439+ GC.SuppressFinalize (this);
2440+
2441+ handle = new HandleRef (this, IntPtr.Zero);
2442+ }
2443+
2444+ public Status State {
2445+ get {
2446+ return pa_operation_get_state (handle);
2447+ }
2448+ }
2449+
2450+ [DllImport ("pulse")]
2451+ private static extern void pa_operation_unref (HandleRef opn);
2452+ [DllImport ("pulse")]
2453+ private static extern Status pa_operation_get_state (HandleRef opn);
2454+ [DllImport ("pulse")]
2455+ private static extern void pa_operation_ref (HandleRef opn);
2456+ [DllImport ("pulse")]
2457+ private static extern void pa_operation_cancel (HandleRef opn);
2458+ }
2459+}
2460
2461=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs'
2462--- StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs 1970-01-01 00:00:00 +0000
2463+++ StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs 2012-02-04 07:53:22 +0000
2464@@ -0,0 +1,509 @@
2465+//
2466+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
2467+//
2468+// PropList.cs is a part of Pulseaudio#
2469+//
2470+// Pulseaudio# is free software: you can redistribute it and/or modify
2471+// it under the terms of the GNU Lesser General Public License as published by
2472+// the Free Software Foundation, either version 3 of the License, or
2473+// (at your option) any later version.
2474+//
2475+// Pulseaudio# is distributed in the hope that it will be useful,
2476+// but WITHOUT ANY WARRANTY; without even the implied warranty of
2477+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2478+// GNU Lesser General Public License for more details.
2479+//
2480+// You should have received a copy of the GNU Lesser General Public License
2481+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
2482+//
2483+
2484+using System;
2485+using System.Collections.Generic;
2486+using System.Runtime.InteropServices;
2487+
2488+namespace Pulseaudio
2489+{
2490+ public enum Properties {
2491+ /// <summary>
2492+ /// For streams: localized media name, formatted as UTF-8. e.g. "Guns'N'Roses: Civil War".
2493+ /// </summary>
2494+ MediaName,
2495+ /// <summary>
2496+ /// For streams: localized media title if applicable, formatted as UTF-8. e.g. "Civil War"
2497+ /// </summary>
2498+ MediaTitle,
2499+ /// <summary>
2500+ /// For streams: localized media artist if applicable, formatted as UTF-8. e.g. "Guns'N'Roses"
2501+ /// </summary>
2502+ MediaArtist,
2503+ /// <summary>
2504+ /// For streams: localized media copyright string if applicable, formatted as UTF-8. e.g. "Evil Record Corp."
2505+ /// </summary>
2506+ MediaCopyright,
2507+ /// <summary>
2508+ /// For streams: localized media generator software string if applicable, formatted as UTF-8. e.g. "Foocrop AudioFrobnicator"
2509+ /// </summary>
2510+ MediaSoftware,
2511+ /// <summary>
2512+ /// For streams: media language if applicable, in standard POSIX format. e.g. "de_DE"
2513+ /// </summary>
2514+ MediaLanguage,
2515+ /// <summary>
2516+ /// For streams: source filename if applicable, in URI format or local path. e.g. "/home/lennart/music/foobar.ogg"
2517+ /// </summary>
2518+ MediaFilename,
2519+ /// <summary>
2520+ /// For streams: icon for the media. A binary blob containing PNG image data
2521+ /// </summary>
2522+ MediaIcon,
2523+ /// <summary>
2524+ /// For streams: an XDG icon name for the media. e.g. "audio-x-mp3"
2525+ /// </summary>
2526+ MediaIconName,
2527+ /// <summary>
2528+ /// For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y"
2529+ /// </summary>
2530+ MediaRole,
2531+ /// <summary>
2532+ /// For event sound streams: XDG event sound name. e.g. "message-new-email" (Event sound streams are those with media.role set to "event")
2533+ /// </summary>
2534+ EventID,
2535+ /// <summary>
2536+ /// For event sound streams: localized human readable one-line description of the event, formatted as UTF-8. e.g. "Email from lennart@example.com received."
2537+ /// </summary>
2538+ EventDescription,
2539+ /// <summary>
2540+ /// For event sound streams: absolute horizontal mouse position on the screen if the event sound was triggered by a mouse click, integer formatted as text string. e.g. "865"
2541+ /// </summary>
2542+ EventMouseX,
2543+ /// <summary>
2544+ /// For event sound streams: absolute vertical mouse position on the screen if the event sound was triggered by a mouse click, integer formatted as text string. e.g. "432"
2545+ /// </summary>
2546+ EventMouseY,
2547+ /// <summary>
2548+ /// For event sound streams: relative horizontal mouse position on the screen if the event sound was triggered by a mouse click, float formatted as text string, ranging from 0.0 (left side of the screen) to 1.0 (right side of the screen). e.g. "0.65"
2549+ /// </summary>
2550+ EventMouseHPos,
2551+ /// <summary>
2552+ /// For event sound streams: relative vertical mouse position on the screen if the event sound was triggered by a mouse click, float formatted as text string, ranging from 0.0 (top of the screen) to 1.0 (bottom of the screen). e.g. "0.43"
2553+ /// </summary>
2554+ EventMouseVPos,
2555+ /// <summary>
2556+ /// For event sound streams: mouse button that triggered the event if applicable, integer formatted as string with 0=left, 1=middle, 2=right. e.g. "0"
2557+ /// </summary>
2558+ EventMouseButton,
2559+ /// <summary>
2560+ /// For streams that belong to a window on the screen: localized window title. e.g. "Totem Music Player"
2561+ /// </summary>
2562+ WindowName,
2563+ /// <summary>
2564+ /// For streams that belong to a window on the screen: a textual id for identifying a window logically. e.g. "org.gnome.Totem.MainWindow"
2565+ /// </summary>
2566+ WindowID,
2567+ /// <summary>
2568+ /// For streams that belong to a window on the screen: window icon. A binary blob containing PNG image data
2569+ /// </summary>
2570+ WindowIcon,
2571+ /// <summary>
2572+ /// For streams that belong to a window on the screen: an XDG icon name for the window. e.g. "totem"
2573+ /// </summary>
2574+ WindowIconName,
2575+ /// <summary>
2576+ /// For streams that belong to a window on the screen: absolute horizontal window position on the screen, integer formatted as text string. e.g. "865". \since 0.9.17
2577+ /// </summary>
2578+ WindowX,
2579+ /// <summary>
2580+ /// For streams that belong to a window on the screen: absolute vertical window position on the screen, integer formatted as text string. e.g. "343". \since 0.9.17
2581+ /// </summary>
2582+ WindowY,
2583+ /// <summary>
2584+ /// For streams that belong to a window on the screen: window width on the screen, integer formatted as text string. e.g. "365". \since 0.9.17
2585+ /// </summary>
2586+ WindowWidth,
2587+ /// <summary>
2588+ /// For streams that belong to a window on the screen: window height on the screen, integer formatted as text string. e.g. "643". \since 0.9.17
2589+ /// </summary>
2590+ WindowHeight,
2591+ /// <summary>
2592+ /// For streams that belong to a window on the screen: relative position of the window center on the screen, float formatted as text string, ranging from 0.0 (left side of the screen) to 1.0 (right side of the screen). e.g. "0.65". \since 0.9.17
2593+ /// </summary>
2594+ WindowHPos,
2595+ /// <summary>
2596+ /// For streams that belong to a window on the screen: relative position of the window center on the screen, float formatted as text string, ranging from 0.0 (top of the screen) to 1.0 (bottom of the screen). e.g. "0.43". \since 0.9.17
2597+ /// </summary>
2598+ WindowVPos,
2599+ /// <summary>
2600+ /// For streams that belong to a window on the screen: if the windowing system supports multiple desktops, a comma seperated list of indexes of the desktops this window is visible on. If this property is an empty string, it is visible on all desktops (i.e. 'sticky'). The first desktop is 0. e.g. "0,2,3" \since 0.9.18
2601+ /// </summary>
2602+ WindowDesktop,
2603+ /// <summary>
2604+ /// For streams that belong to an X11 window on the screen: the X11 display string. e.g. ":0.0"
2605+ /// </summary>
2606+ WindowX11Display,
2607+ /// <summary>
2608+ /// For streams that belong to an X11 window on the screen: the X11 screen the window is on, an integer formatted as string. e.g. "0"
2609+ /// </summary>
2610+ WindowX11Screen,
2611+ /// <summary>
2612+ /// For streams that belong to an X11 window on the screen: the X11 monitor the window is on, an integer formatted as string. e.g. "0"
2613+ /// </summary>
2614+ WindowX11Monitor,
2615+ /// <summary>
2616+ /// For streams that belong to an X11 window on the screen: the window XID, an integer formatted as string. e.g. "25632"
2617+ /// </summary>
2618+ WindowX11XID,
2619+ /// <summary>
2620+ /// For clients/streams: localized human readable application name. e.g. "Totem Music Player"
2621+ /// </summary>
2622+ ApplicationName,
2623+ /// <summary>
2624+ /// For clients/streams: a textual id for identifying an application logically. e.g. "org.gnome.Totem"
2625+ /// </summary>
2626+ ApplicationID,
2627+ /// <summary>
2628+ /// For clients/streams: a version string e.g. "0.6.88"
2629+ /// </summary>
2630+ ApplicationVersion,
2631+ /// <summary>
2632+ /// For clients/streams: an XDG icon name for the application. e.g. "totem"
2633+ /// </summary>
2634+ ApplicationIconName,
2635+ /// <summary>
2636+ /// For clients/streams: application language if applicable, in standard POSIX format. e.g. "de_DE"
2637+ /// </summary>
2638+ ApplicationLanguage,
2639+ /// <summary>
2640+ /// For clients/streams on UNIX: application process PID, an integer formatted as string. e.g. "4711"
2641+ /// </summary>
2642+ ApplicationPID,
2643+ /// <summary>
2644+ /// For clients/streams: application process name. e.g. "totem"
2645+ /// </summary>
2646+ ApplicationProcessBinary,
2647+ /// <summary>
2648+ /// For clients/streams: application user name. e.g. "lennart"
2649+ /// </summary>
2650+ ApplicationProcessUser,
2651+ /// <summary>
2652+ /// For clients/streams: host name the application runs on. e.g. "omega"
2653+ /// </summary>
2654+ ApplicationProcessHost,
2655+ /// <summary>
2656+ /// For clients/streams: the D-Bus host id the application runs on. e.g. "543679e7b01393ed3e3e650047d78f6e"
2657+ /// </summary>
2658+ ApplicationProcessMachineID,
2659+ /// <summary>
2660+ /// For clients/streams: an id for the login session the application runs in. On Unix the value of $XDG_SESSION_COOKIE. e.g. "543679e7b01393ed3e3e650047d78f6e-1235159798.76193-190367717"
2661+ /// </summary>
2662+ ApplicationProcessSessionID,
2663+ /// <summary>
2664+ /// For devices: device string in the underlying audio layer's format. e.g. "surround51:0"
2665+ /// </summary>
2666+ DeviceString,
2667+ /// <summary>
2668+ /// For devices: API this device is access with. e.g. "alsa"
2669+ /// </summary>
2670+ DeviceAPI,
2671+ /// <summary>
2672+ /// For devices: localized human readable device one-line description, e.g. "Foobar Industries USB Headset 2000+ Ultra"
2673+ /// </summary>
2674+ DeviceDescription,
2675+ /// <summary>
2676+ /// For devices: bus path to the device in the OS' format. e.g. "/sys/bus/pci/devices/0000:00:1f.2"
2677+ /// </summary>
2678+ DeviceBusPath,
2679+ /// <summary>
2680+ /// For devices: serial number if applicable. e.g. "4711-0815-1234"
2681+ /// </summary>
2682+ DeviceSerial,
2683+ /// <summary>
2684+ /// For devices: vendor ID if applicable. e.g. 1274
2685+ /// </summary>
2686+ DeviceVendorID,
2687+ /// <summary>
2688+ /// For devices: vendor name if applicable. e.g. "Foocorp Heavy Industries"
2689+ /// </summary>
2690+ DeviceVendorName,
2691+ /// <summary>
2692+ /// For devices: product ID if applicable. e.g. 4565
2693+ /// </summary>
2694+ DeviceProductID,
2695+ /// <summary>
2696+ /// For devices: product name if applicable. e.g. "SuperSpeakers 2000 Pro"
2697+ /// </summary>
2698+ DeviceProductName,
2699+ /// <summary>
2700+ /// For devices: device class. One of "sound", "modem", "monitor", "filter"
2701+ /// </summary>
2702+ DeviceClass,
2703+ /// <summary>
2704+ /// For devices: form factor if applicable. One of "internal", "speaker", "handset", "tv", "webcam", "microphone", "headset", "headphone", "hands-free", "car", "hifi", "computer", "portable"
2705+ /// </summary>
2706+ DeviceFormFactor,
2707+ /// <summary>
2708+ /// For devices: bus of the device if applicable. One of "isa", "pci", "usb", "firewire", "bluetooth"
2709+ /// </summary>
2710+ DeviceBus,
2711+ /// <summary>
2712+ /// For devices: an XDG icon name for the device. e.g. "sound-card-speakers-usb"
2713+ /// </summary>
2714+ DeviceIconName,
2715+ /// <summary>
2716+ /// For devices: access mode of the device if applicable. One of "mmap", "mmap_rewrite", "serial"
2717+ /// </summary>
2718+ DeviceAccessMode,
2719+ /// <summary>
2720+ /// For filter devices: master device id if applicable.
2721+ /// </summary>
2722+ DeviceMasterDevice,
2723+ /// <summary>
2724+ /// For devices: buffer size in bytes, integer formatted as string.
2725+ /// </summary>
2726+ DeviceBufferSize,
2727+ /// <summary>
2728+ /// For devices: fragment size in bytes, integer formatted as string.
2729+ /// </summary>
2730+ DeviceFragmentSize,
2731+ /// <summary>
2732+ /// For devices: profile identifier for the profile this devices is in. e.g. "analog-stereo", "analog-surround-40", "iec958-stereo", ...
2733+ /// </summary>
2734+ DeviceProfileName,
2735+ /// <summary>
2736+ /// For devices: human readable one-line description of the profile this device is in. e.g. "Analog Stereo", ...
2737+ /// </summary>
2738+ DeviceProfileDescription,
2739+ /// <summary>
2740+ /// For devices: intended use. A comma seperated list of roles (see PA_PROP_MEDIA_ROLE) this device is particularly well suited for, due to latency, quality or form factor. \since 0.9.16
2741+ /// </summary>
2742+ DeviceIntendedRoles,
2743+ /// <summary>
2744+ /// For modules: the author's name, formatted as UTF-8 string. e.g. "Lennart Poettering"
2745+ /// </summary>
2746+ ModuleAuthor,
2747+ /// <summary>
2748+ /// For modules: a human readable one-line description of the module's purpose formatted as UTF-8. e.g. "Frobnicate sounds with a flux compensator"
2749+ /// </summary>
2750+ ModuleDescription,
2751+ /// <summary>
2752+ /// For modules: a human readable usage description of the module's arguments formatted as UTF-8
2753+ /// </summary>
2754+ ModuleUsage,
2755+ /// <summary>
2756+ /// For modules: a version string for the module. e.g. "0.9.15"
2757+ /// </summary>
2758+ ModuleVersion
2759+ }
2760+
2761+ public class PropList : IDisposable
2762+ {
2763+ private static readonly Dictionary<Properties, string> propertyTable = new Dictionary<Properties, string> {
2764+ {Properties.MediaName, "media.name"},
2765+ {Properties.MediaTitle, "media.title"},
2766+ {Properties.MediaArtist, "media.artist"},
2767+ {Properties.MediaCopyright, "media.copyright"},
2768+ {Properties.MediaSoftware, "media.software"},
2769+ {Properties.MediaLanguage, "media.language"},
2770+ {Properties.MediaFilename, "media.filename"},
2771+ {Properties.MediaIconName, "media.icon_name"},
2772+ {Properties.MediaRole, "media.role"},
2773+ {Properties.EventID, "event.id"},
2774+ {Properties.EventDescription, "event.description"},
2775+ {Properties.EventMouseX, "event.mouse.x"},
2776+ {Properties.EventMouseY, "event.mouse.y"},
2777+ {Properties.EventMouseHPos, "event.mouse.hpos"},
2778+ {Properties.EventMouseVPos, "event.mouse.vpos"},
2779+ {Properties.EventMouseButton, "event.mouse.button"},
2780+ {Properties.WindowName, "window.name"},
2781+ {Properties.WindowID, "window.id"},
2782+ {Properties.WindowIconName, "window.icon_name"},
2783+ {Properties.WindowX, "window.x"},
2784+ {Properties.WindowY, "window.y"},
2785+ {Properties.WindowWidth, "window.width"},
2786+ {Properties.WindowHeight, "window.height"},
2787+ {Properties.WindowHPos, "window.hpos"},
2788+ {Properties.WindowVPos, "window.vpos"},
2789+ {Properties.WindowDesktop, "window.desktop"},
2790+ {Properties.WindowX11Display, "window.x11.display"},
2791+ {Properties.WindowX11Screen, "window.x11.screen"},
2792+ {Properties.WindowX11Monitor, "window.x11.monitor"},
2793+ {Properties.WindowX11XID, "window.x11.xid"},
2794+ {Properties.ApplicationName, "application.name"},
2795+ {Properties.ApplicationID, "application.id"},
2796+ {Properties.ApplicationVersion, "application.version"},
2797+ {Properties.ApplicationIconName, "application.icon_name"},
2798+ {Properties.ApplicationLanguage, "application.language"},
2799+ {Properties.ApplicationPID, "application.process.id"},
2800+ {Properties.ApplicationProcessBinary, "application.process.binary"},
2801+ {Properties.ApplicationProcessUser, "application.process.user"},
2802+ {Properties.ApplicationProcessHost, "application.process.host"},
2803+ {Properties.ApplicationProcessMachineID, "application.process.machine_id"},
2804+ {Properties.ApplicationProcessSessionID, "application.process.session_id"},
2805+ {Properties.DeviceString, "device.string"},
2806+ {Properties.DeviceAPI, "device.api"},
2807+ {Properties.DeviceDescription, "device.description"},
2808+ {Properties.DeviceBusPath, "device.bus_path"},
2809+ {Properties.DeviceSerial, "device.serial"},
2810+ {Properties.DeviceVendorID, "device.vendor.id"},
2811+ {Properties.DeviceVendorName, "device.vendor.name"},
2812+ {Properties.DeviceProductID, "device.product.id"},
2813+ {Properties.DeviceProductName, "device.product.name"},
2814+ {Properties.DeviceClass, "device.class"},
2815+ {Properties.DeviceFormFactor, "device.form_factor"},
2816+ {Properties.DeviceBus, "device.bus"},
2817+ {Properties.DeviceIconName, "device.icon_name"},
2818+ {Properties.DeviceAccessMode, "device.access_mode"},
2819+ {Properties.DeviceMasterDevice, "device.master_device"},
2820+ {Properties.DeviceBufferSize, "device.buffering.buffer_size"},
2821+ {Properties.DeviceFragmentSize, "device.buffering.fragment_size"},
2822+ {Properties.DeviceProfileName, "device.profile.name"},
2823+ {Properties.DeviceProfileDescription, "device.profile.description"},
2824+ {Properties.DeviceIntendedRoles, "device.intended_roles"},
2825+ {Properties.ModuleAuthor, "module.author"},
2826+ {Properties.ModuleDescription, "module.description"},
2827+ {Properties.ModuleUsage, "module.version"},
2828+ {Properties.ModuleVersion, "module.version"}
2829+ };
2830+ private HandleRef handle;
2831+ private bool needDispose;
2832+
2833+ public PropList ()
2834+ {
2835+ IntPtr listPtr = pa_proplist_new ();
2836+ if (listPtr == IntPtr.Zero) {
2837+ throw new NullReferenceException ();
2838+ }
2839+ handle = new HandleRef (this, listPtr);
2840+ needDispose = true;
2841+ }
2842+
2843+ /// <summary>
2844+ /// Internal-only PropList constructor taking an unmanaged pointer.
2845+ /// </summary>
2846+ /// <param name="listPtr">
2847+ /// A <see cref="IntPtr"/>. Unmanaged pointer to a pa_proplist.
2848+ /// </param>
2849+ /// <remarks>
2850+ /// It is assumed that native code owns the pa_proplist pointer. As such, it will not be free'd on Dispose ().
2851+ /// If the managed code should own this pointer, set needDispose to true after calling this constructor.
2852+ /// </remarks>
2853+ internal PropList (IntPtr listPtr)
2854+ {
2855+ if (listPtr == IntPtr.Zero) {
2856+ throw new NullReferenceException ();
2857+ }
2858+ handle = new HandleRef (this, listPtr);
2859+ needDispose = false;
2860+ GC.SuppressFinalize (this);
2861+ }
2862+
2863+ ~PropList ()
2864+ {
2865+ if (needDispose) {
2866+ pa_proplist_free (handle);
2867+ }
2868+ }
2869+
2870+ public void Dispose ()
2871+ {
2872+ if (needDispose) {
2873+ pa_proplist_free (handle);
2874+ }
2875+ handle = new HandleRef (this, IntPtr.Zero);
2876+ needDispose = false;
2877+
2878+ GC.SuppressFinalize (this);
2879+ }
2880+
2881+ public byte[] this[string key]
2882+ {
2883+ get {
2884+ IntPtr data = new IntPtr ();
2885+ IntPtr size = new IntPtr ();
2886+ pa_proplist_get (handle, key, ref data, ref size);
2887+
2888+ byte[] retVal = new byte[size.ToInt32 ()];
2889+ Marshal.Copy (data, retVal, 0, size.ToInt32 ());
2890+
2891+ return retVal;
2892+ }
2893+ set {
2894+ if (value == null) {
2895+ throw new ArgumentNullException ("value", "Setting a PropList key to 'null' is unsupported");
2896+ }
2897+ pa_proplist_set (handle, key, value, new IntPtr (Marshal.SizeOf (value[0]) * value.Length));
2898+ }
2899+ }
2900+
2901+ public string this[Properties prop]
2902+ {
2903+ get {
2904+ return Marshal.PtrToStringAnsi (pa_proplist_gets (handle, propertyTable[prop]));
2905+ }
2906+ set {
2907+ pa_proplist_sets (handle, propertyTable[prop], value);
2908+ }
2909+ }
2910+
2911+ public bool Empty {
2912+ get {
2913+ // TODO: Work out why pa_proplist_isempty doesn't return 0 when the list is empty.
2914+ return (Count == 0);
2915+ }
2916+ }
2917+
2918+ public uint Count {
2919+ get {
2920+ return pa_proplist_size (handle);
2921+ }
2922+ }
2923+
2924+ public void Clear ()
2925+ {
2926+ pa_proplist_clear (handle);
2927+ }
2928+
2929+ public PropList Copy ()
2930+ {
2931+ PropList clone = new PropList (pa_proplist_copy (handle));
2932+ clone.needDispose = true;
2933+ GC.ReRegisterForFinalize (clone);
2934+ return clone;
2935+ }
2936+
2937+ public IEnumerable<string> Keys {
2938+ get {
2939+ IntPtr cookie = new IntPtr (0);
2940+ IntPtr key;
2941+ key = pa_proplist_iterate (handle, ref cookie);
2942+ while (key != IntPtr.Zero) {
2943+ yield return Marshal.PtrToStringAnsi (key);
2944+ key = pa_proplist_iterate (handle, ref cookie);
2945+ }
2946+ yield break;
2947+ }
2948+ }
2949+
2950+ [DllImport ("pulse")]
2951+ private static extern int pa_proplist_isempty (HandleRef list);
2952+ [DllImport ("pulse")]
2953+ private static extern IntPtr pa_proplist_new ();
2954+ [DllImport ("pulse")]
2955+ private static extern void pa_proplist_clear (HandleRef list);
2956+ [DllImport ("pulse")]
2957+ private static extern uint pa_proplist_size (HandleRef list);
2958+ [DllImport ("pulse")]
2959+ private static extern int pa_proplist_sets (HandleRef list, string key, string value);
2960+ [DllImport ("pulse")]
2961+ private static extern int pa_proplist_set (HandleRef list, string key, byte[] data, IntPtr size);
2962+ [DllImport ("pulse")]
2963+ private static extern IntPtr pa_proplist_gets (HandleRef list, string key);
2964+ [DllImport ("pulse")]
2965+ private static extern int pa_proplist_get (HandleRef list, string key, ref IntPtr data, ref IntPtr size);
2966+ [DllImport ("pulse")]
2967+ private static extern IntPtr pa_proplist_iterate (HandleRef list, ref IntPtr cookie);
2968+ [DllImport ("pulse")]
2969+ private static extern IntPtr pa_proplist_copy (HandleRef list);
2970+ [DllImport ("pulse")]
2971+ private static extern void pa_proplist_free (HandleRef list);
2972+ }
2973+}
2974
2975=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs'
2976--- StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs 1970-01-01 00:00:00 +0000
2977+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs 2012-02-04 07:53:22 +0000
2978@@ -0,0 +1,89 @@
2979+//
2980+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
2981+//
2982+// Sample.cs is a part of Pulseaudio#
2983+//
2984+// Pulseaudio# is free software: you can redistribute it and/or modify
2985+// it under the terms of the GNU Lesser General Public License as published by
2986+// the Free Software Foundation, either version 3 of the License, or
2987+// (at your option) any later version.
2988+//
2989+// Pulseaudio# is distributed in the hope that it will be useful,
2990+// but WITHOUT ANY WARRANTY; without even the implied warranty of
2991+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2992+// GNU Lesser General Public License for more details.
2993+//
2994+// You should have received a copy of the GNU Lesser General Public License
2995+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
2996+//
2997+
2998+using System;
2999+using System.Runtime.InteropServices;
3000+
3001+namespace Pulseaudio
3002+{
3003+ public enum SampleFormat
3004+ {
3005+ U8,
3006+ /**< Unsigned 8 Bit PCM */
3007+
3008+ ALAW,
3009+ /**< 8 Bit a-Law */
3010+
3011+ ULAW,
3012+ /**< 8 Bit mu-Law */
3013+
3014+ S16LE,
3015+ /**< Signed 16 Bit PCM, little endian (PC) */
3016+
3017+ S16BE,
3018+ /**< Signed 16 Bit PCM, big endian */
3019+
3020+ Float32LE,
3021+ /**< 32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0 */
3022+
3023+ Float32BE,
3024+ /**< 32 Bit IEEE floating point, big endian, range -1.0 to 1.0 */
3025+
3026+ S32LE,
3027+ /**< Signed 32 Bit PCM, little endian (PC) */
3028+
3029+ S32BE,
3030+ /**< Signed 32 Bit PCM, big endian */
3031+
3032+ S24LE,
3033+ /**< Signed 24 Bit PCM packed, little endian (PC). \since 0.9.15 */
3034+
3035+ S24BE,
3036+ /**< Signed 24 Bit PCM packed, big endian. \since 0.9.15 */
3037+
3038+ S24in32LE,
3039+ /**< Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC). \since 0.9.15 */
3040+
3041+ S24in32BE,
3042+ /**< Signed 24 Bit PCM in LSB of 32 Bit words, big endian. \since 0.9.15 */
3043+
3044+ Max,
3045+ /**< Upper limit of valid sample types */
3046+
3047+ Invalid = -1
3048+ /**< An invalid value */
3049+ }
3050+
3051+ [StructLayout (LayoutKind.Sequential)]
3052+ public struct SampleSpec
3053+ {
3054+ public SampleFormat format;
3055+ public UInt32 rate;
3056+ public Byte channels;
3057+
3058+ public UInt64 BytesPerSecond {
3059+ get {
3060+ return (UInt64) pa_bytes_per_second (ref this);
3061+ }
3062+ }
3063+
3064+ [DllImport ("pulse")]
3065+ private static extern UIntPtr pa_bytes_per_second (ref SampleSpec spec);
3066+ }
3067+}
3068
3069=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs'
3070--- StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs 1970-01-01 00:00:00 +0000
3071+++ StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs 2012-02-04 07:53:22 +0000
3072@@ -0,0 +1,120 @@
3073+//
3074+// ServerInfo.cs
3075+//
3076+// Author:
3077+// Christopher James Halse Rogers <raof@ubuntu.com>
3078+//
3079+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3080+//
3081+// This program is free software: you can redistribute it and/or modify
3082+// it under the terms of the GNU Lesser General Public License as published by
3083+// the Free Software Foundation, either version 3 of the License, or
3084+// (at your option) any later version.
3085+//
3086+// This program is distributed in the hope that it will be useful,
3087+// but WITHOUT ANY WARRANTY; without even the implied warranty of
3088+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3089+// GNU Lesser General Public License for more details.
3090+//
3091+// You should have received a copy of the GNU Lesser General Public License
3092+// along with this program. If not, see <http://www.gnu.org/licenses/>.
3093+
3094+using System;
3095+using System.Runtime.InteropServices;
3096+
3097+namespace Pulseaudio
3098+{
3099+ [StructLayout(LayoutKind.Sequential)]
3100+ internal class NativeServerInfo
3101+ {
3102+ public IntPtr user_name;
3103+ public IntPtr host_name;
3104+ public IntPtr server_version;
3105+ public IntPtr server_name;
3106+ public SampleSpec sample_spec;
3107+ public IntPtr default_sink_name;
3108+ public IntPtr default_source_name;
3109+ public UInt32 cookie;
3110+ public ChannelMap channel_map;
3111+ }
3112+
3113+ public class ServerInfo
3114+ {
3115+ internal ServerInfo (NativeServerInfo info)
3116+ {
3117+ UserName = Marshal.PtrToStringAnsi (info.user_name);
3118+ HostName = Marshal.PtrToStringAnsi (info.host_name);
3119+ Version = Marshal.PtrToStringAnsi (info.server_version);
3120+ Name = Marshal.PtrToStringAnsi (info.server_name);
3121+ DefaultSampleSpec = info.sample_spec;
3122+ DefaultSinkName = Marshal.PtrToStringAnsi (info.default_sink_name);
3123+ DefaultSourceName = Marshal.PtrToStringAnsi (info.default_source_name);
3124+ Cookie = info.cookie;
3125+ DefaultChannelMap = info.channel_map;
3126+ }
3127+
3128+ /// <summary>
3129+ /// Name of the user running the daemon process
3130+ /// </summary>
3131+ public string UserName {
3132+ get;
3133+ private set;
3134+ }
3135+ /// <summary>
3136+ /// Host name of the machine the daemon is running on
3137+ /// </summary>
3138+ public string HostName {
3139+ get;
3140+ private set;
3141+ }
3142+ /// <summary>
3143+ /// Version string of the daemon
3144+ /// </summary>
3145+ public string Version {
3146+ get;
3147+ private set;
3148+ }
3149+ /// <summary>
3150+ /// Package name of the daemon (usually "pulseaudio")
3151+ /// </summary>
3152+ public string Name {
3153+ get;
3154+ private set;
3155+ }
3156+ /// <summary>
3157+ /// Default sample specification for this daemon
3158+ /// </summary>
3159+ public SampleSpec DefaultSampleSpec {
3160+ get;
3161+ private set;
3162+ }
3163+ /// <summary>
3164+ /// Name of the default sink for this daemon
3165+ /// </summary>
3166+ public string DefaultSinkName {
3167+ get;
3168+ private set;
3169+ }
3170+ /// <summary>
3171+ /// Name of the default source for this daemon
3172+ /// </summary>
3173+ public string DefaultSourceName {
3174+ get;
3175+ private set;
3176+ }
3177+ /// <summary>
3178+ /// A random cookie identifying this daemon process
3179+ /// </summary>
3180+ public UInt32 Cookie {
3181+ get;
3182+ private set;
3183+ }
3184+ /// <summary>
3185+ /// Default channel map for this server
3186+ /// </summary>
3187+ public ChannelMap DefaultChannelMap {
3188+ get;
3189+ private set;
3190+ }
3191+ }
3192+}
3193
3194=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs'
3195--- StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs 1970-01-01 00:00:00 +0000
3196+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs 2012-02-04 07:53:22 +0000
3197@@ -0,0 +1,346 @@
3198+//
3199+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3200+//
3201+// Sink.cs is a part of Pulseaudio#
3202+//
3203+// Pulseaudio# is free software: you can redistribute it and/or modify
3204+// it under the terms of the GNU Lesser General Public License as published by
3205+// the Free Software Foundation, either version 3 of the License, or
3206+// (at your option) any later version.
3207+//
3208+// Pulseaudio# is distributed in the hope that it will be useful,
3209+// but WITHOUT ANY WARRANTY; without even the implied warranty of
3210+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3211+// GNU Lesser General Public License for more details.
3212+//
3213+// You should have received a copy of the GNU Lesser General Public License
3214+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
3215+//
3216+
3217+using System;
3218+using System.Collections.Generic;
3219+using System.Linq;
3220+using System.Runtime.InteropServices;
3221+
3222+namespace Pulseaudio
3223+{
3224+ public enum SinkState {
3225+ Invalid = -1,
3226+ /**< This state is used when the server does not support sink state introspection \since 0.9.15 */
3227+
3228+ Running = 0,
3229+ /**< Running, sink is playing and used by at least one non-corked sink-input \since 0.9.15 */
3230+
3231+ Idle = 1,
3232+ /**< When idle, the sink is playing but there is no non-corked sink-input attached to it \since 0.9.15 */
3233+
3234+ Suspended = 2,
3235+ /**< When suspended, actual sink access can be closed, for instance \since 0.9.15 */
3236+ }
3237+
3238+ [Flags]
3239+ public enum SinkFlags {
3240+ HardwareVolume = 0x0001,
3241+ /**< Supports hardware volume control */
3242+
3243+ LatencyQuery = 0x0002,
3244+ /**< Supports latency querying */
3245+
3246+ IsHardware = 0x0004,
3247+ /**< Is a hardware sink of some kind, in contrast to
3248+ * "virtual"/software sinks \since 0.9.3 */
3249+
3250+ IsNetwork = 0x0008,
3251+ /**< Is a networked sink of some kind. \since 0.9.7 */
3252+
3253+ HardwareMute = 0x0010,
3254+ /**< Supports hardware mute control \since 0.9.11 */
3255+
3256+ dBVolume = 0x0020,
3257+ /**< Volume can be translated to dB with pa_sw_volume_to_dB()
3258+ * \since 0.9.11 */
3259+
3260+ FlatVolume = 0x0040,
3261+ /**< This sink is in flat volume mode, i.e. always the maximum of
3262+ * the volume of all connected inputs. \since 0.9.15 */
3263+
3264+ DynamicLatency = 0x0080
3265+ /**< The latency can be adjusted dynamically depending on the
3266+ * needs of the connected streams. \since 0.9.15 */
3267+ }
3268+
3269+ [StructLayout (LayoutKind.Sequential)]
3270+ public class SinkInfo
3271+ {
3272+ IntPtr name; /**< Name of the sink */
3273+ public UInt32 index; /**< Index of the sink */
3274+ IntPtr description; /**< Description of this sink */
3275+ public SampleSpec sample_spec; /**< Sample spec of this sink */
3276+ public ChannelMap channel_map; /**< Channel map */
3277+ public UInt32 owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX */
3278+ public Volume volume; /**< Volume of the sink */
3279+ public int mute; /**< Mute switch of the sink */
3280+ public UInt32 monitor_source; /**< Index of the monitor source connected to this sink */
3281+ IntPtr monitor_source_name; /**< The name of the monitor source */
3282+ public UInt64 latency; /**< Length of queued audio in the output buffer. */
3283+ IntPtr driver; /**< Driver name. */
3284+ public SinkFlags flags; /**< Flags */
3285+ IntPtr proplist; /**< Property list \since 0.9.11 */
3286+ public UInt64 configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */
3287+ public UInt32 base_volume; /**< Some kind of "base" volume that refers to unamplified/unattenuated volume in the context of the output device. \since 0.9.15 */
3288+ public SinkState state; /**< State \since 0.9.15 */
3289+ public UInt32 n_volume_steps; /**< Number of volume steps for sinks which do not support arbitrary volumes. \since 0.9.15 */
3290+ public UInt32 card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */
3291+
3292+ public string Name {
3293+ get {
3294+ return Marshal.PtrToStringAnsi (name);
3295+ }
3296+ }
3297+
3298+ public string Description {
3299+ get { return Marshal.PtrToStringAnsi (description);
3300+ }
3301+ }
3302+
3303+ public string MonitorSourceName {
3304+ get {
3305+ return Marshal.PtrToStringAnsi (monitor_source_name);
3306+ }
3307+ }
3308+
3309+ public string Driver {
3310+ get {
3311+ return Marshal.PtrToStringAnsi (driver);
3312+ }
3313+ }
3314+
3315+ public PropList Properties {
3316+ get {
3317+ return new PropList (proplist);
3318+ }
3319+ }
3320+
3321+ public SinkInfo()
3322+ {
3323+ }
3324+ }
3325+
3326+ public class Sink : IDisposable {
3327+ private void UpdateFromInfo (SinkInfo i)
3328+ {
3329+ if (info.volume != i.volume || info.mute != i.mute) {
3330+ info = i;
3331+ Description = i.Description;
3332+ MonitorSourceName = i.MonitorSourceName;
3333+ Driver = i.Driver;
3334+ EventHandler<VolumeChangedEventArgs> handler;
3335+ lock (eventHandlerLock) {
3336+ handler = _volumeChangedHandler;
3337+ }
3338+ if (handler != null) {
3339+ handler (this, new VolumeChangedEventArgs (i.volume));
3340+ }
3341+ } else {
3342+ info = i;
3343+ Description = i.Description;
3344+ MonitorSourceName = i.MonitorSourceName;
3345+ Driver = i.Driver;
3346+ }
3347+ }
3348+
3349+ private bool disposed = false;
3350+ private SinkInfo info;
3351+ private Context context;
3352+
3353+ public Sink (Context c, SinkInfo info)
3354+ {
3355+ context = c;
3356+ this.info = info;
3357+ Name = info.Name;
3358+ Description = info.Description;
3359+ MonitorSourceName = info.MonitorSourceName;
3360+ Driver = info.Driver;
3361+ Properties = info.Properties.Copy ();
3362+ context.SinkEvent += HandleRawSinkEvent;
3363+ }
3364+
3365+ public void Dispose ()
3366+ {
3367+ Dispose (true);
3368+ GC.SuppressFinalize (this);
3369+ }
3370+
3371+ protected virtual void Dispose (bool explicitlyCalled)
3372+ {
3373+ if (!disposed) {
3374+ if (explicitlyCalled) {
3375+ //Unregister our server info callbacks
3376+ context.SinkEvent -= HandleRawSinkEvent;
3377+ }
3378+ disposed = true;
3379+ }
3380+ }
3381+
3382+ public string Name {
3383+ get; private set;
3384+ }
3385+
3386+ public string Description {
3387+ get; private set;
3388+ }
3389+
3390+ public PropList Properties {
3391+ get; private set;
3392+ }
3393+
3394+ public UInt32 Index {
3395+ get {
3396+ return info.index;
3397+ }
3398+ }
3399+
3400+ public SampleSpec SampleSpec {
3401+ get {
3402+ //SampleSpec is a struct, so this passes a copy.
3403+ return info.sample_spec;
3404+ }
3405+ }
3406+
3407+ public ChannelMap ChannelMap {
3408+ get {
3409+ //ChannelMap is a struct, so this returns a copy.
3410+ return info.channel_map;
3411+ }
3412+ }
3413+ public UInt32 OwnerModule {
3414+ get {
3415+ return info.owner_module;
3416+ }
3417+ }
3418+
3419+ public Volume Volume {
3420+ get {
3421+ return info.volume.Copy ();
3422+ }
3423+ }
3424+
3425+ public bool Mute {
3426+ get {
3427+ return (info.mute > 0);
3428+ }
3429+ }
3430+
3431+ public UInt32 MonitorSource {
3432+ get {
3433+ return info.monitor_source;
3434+ }
3435+ }
3436+
3437+ public string MonitorSourceName {
3438+ get; private set;
3439+ }
3440+
3441+ public UInt64 Latency {
3442+ get {
3443+ return info.latency;
3444+ }
3445+ }
3446+
3447+ public string Driver {
3448+ get; private set;
3449+ }
3450+
3451+ public SinkFlags Flags {
3452+ get {
3453+ return info.flags;
3454+ }
3455+ }
3456+
3457+ public UInt64 ConfiguredLatency {
3458+ get {
3459+ return info.configured_latency;
3460+ }
3461+ }
3462+
3463+ public UInt32 BaseVolume {
3464+ get {
3465+ return info.base_volume;
3466+ }
3467+ }
3468+
3469+ public SinkState State {
3470+ get {
3471+ return info.state;
3472+ }
3473+ }
3474+
3475+ public UInt32 NumVolumeSteps {
3476+ get {
3477+ return info.n_volume_steps;
3478+ }
3479+ }
3480+
3481+ public UInt32 Card {
3482+ get {
3483+ return info.card;
3484+ }
3485+ }
3486+
3487+ public delegate void VolumeCallback (Volume vol);
3488+ public Operation GetVolume (VolumeCallback cb)
3489+ {
3490+ return context.GetSinkInfoByIndex (info.index, (SinkInfo i, int eol) =>
3491+ {
3492+ if (eol == 0) {
3493+ cb (i.volume);
3494+ }} );
3495+ }
3496+
3497+ public Operation SetVolume (Volume vol, Context.OperationSuccessCallback cb)
3498+ {
3499+ return context.SetSinkVolume (info.index, vol, cb);
3500+ }
3501+
3502+
3503+ private readonly object eventHandlerLock = new object ();
3504+ private EventHandler<VolumeChangedEventArgs> _volumeChangedHandler;
3505+ public event EventHandler<VolumeChangedEventArgs> VolumeChanged {
3506+ add {
3507+ lock (eventHandlerLock) {
3508+ _volumeChangedHandler += value;
3509+ }
3510+ }
3511+ remove {
3512+ lock (eventHandlerLock) {
3513+ _volumeChangedHandler -= value;
3514+ }
3515+ }
3516+ }
3517+
3518+ void HandleRawSinkEvent (object sender, ServerEventArgs e)
3519+ {
3520+ Context c = sender as Context;
3521+ if (e.Type == EventType.Changed) {
3522+ if (e.index == info.index) {
3523+ Operation o = c.GetSinkInfoByIndex (info.index, (SinkInfo i, int eol) =>
3524+ {
3525+ if (eol == 0) {
3526+ UpdateFromInfo (i);
3527+ }});
3528+ o.Dispose ();
3529+ }
3530+ }
3531+ }
3532+
3533+ public class VolumeChangedEventArgs : EventArgs
3534+ {
3535+ public VolumeChangedEventArgs (Volume vol)
3536+ {
3537+ newVolume = vol;
3538+ }
3539+
3540+ public Volume newVolume { get; private set; }
3541+ }
3542+ }
3543+}
3544
3545=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs'
3546--- StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs 1970-01-01 00:00:00 +0000
3547+++ StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs 2012-02-04 07:53:22 +0000
3548@@ -0,0 +1,179 @@
3549+//
3550+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3551+//
3552+// SinkInput.cs is a part of Pulseaudio#
3553+//
3554+// Pulseaudio# is free software: you can redistribute it and/or modify
3555+// it under the terms of the GNU Lesser General Public License as published by
3556+// the Free Software Foundation, either version 3 of the License, or
3557+// (at your option) any later version.
3558+//
3559+// Pulseaudio# is distributed in the hope that it will be useful,
3560+// but WITHOUT ANY WARRANTY; without even the implied warranty of
3561+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3562+// GNU Lesser General Public License for more details.
3563+//
3564+// You should have received a copy of the GNU Lesser General Public License
3565+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
3566+//
3567+
3568+using System;
3569+using System.Runtime.InteropServices;
3570+
3571+namespace Pulseaudio
3572+{
3573+ [StructLayout (LayoutKind.Sequential)]
3574+ internal class NativeSinkInputInfo
3575+ {
3576+ public UInt32 index; /**< Index of the sink input */
3577+ public IntPtr name; /**< Name of the sink input */
3578+ public UInt32 owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */
3579+ public UInt32 client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */
3580+ public UInt32 sink; /**< Index of the connected sink */
3581+ public SampleSpec sample_spec; /**< The sample specification of the sink input */
3582+ public ChannelMap channel_map; /**< Channel map */
3583+ public Volume volume; /**< The volume of this sink input */
3584+ public UInt64 buffer_usec; /**< Latency due to buffering in sink input, see pa_latency_info for details */
3585+ public UInt64 sink_usec; /**< Latency of the sink device, see pa_latency_info for details */
3586+ public IntPtr resample_method; /**< The resampling method used by this sink input. */
3587+ public IntPtr driver; /**< Driver name */
3588+ public int mute; /**< Stream muted \since 0.9.7 */
3589+ public IntPtr prop_handle; /**< Property list \since 0.9.11 */
3590+ }
3591+
3592+ public class SinkInput : IDisposable
3593+ {
3594+ private bool disposed = false;
3595+ private Context ctx;
3596+ private NativeSinkInputInfo info;
3597+
3598+ internal SinkInput (NativeSinkInputInfo info, Context c)
3599+ {
3600+ ctx = c;
3601+ this.info = info;
3602+ Name = Marshal.PtrToStringAnsi (info.name);
3603+ ResampleMethod = Marshal.PtrToStringAnsi (info.resample_method);
3604+ Driver = Marshal.PtrToStringAnsi (info.driver);
3605+ PropList temp = new PropList (info.prop_handle);
3606+ Properties = temp.Copy ();
3607+ ctx.SinkInputEvent += HandleSinkInputEvent;
3608+ }
3609+
3610+ public void Dispose ()
3611+ {
3612+ Dispose (true);
3613+ GC.SuppressFinalize (this);
3614+ }
3615+
3616+ public void Dispose (bool explicitlyCalled)
3617+ {
3618+ if (!disposed) {
3619+ if (explicitlyCalled) {
3620+ Properties.Dispose ();
3621+ ctx.SinkInputEvent -= HandleSinkInputEvent;
3622+ }
3623+ disposed = true;
3624+ }
3625+ }
3626+
3627+ public string Name {
3628+ get;
3629+ private set;
3630+ }
3631+
3632+ public string ResampleMethod {
3633+ get;
3634+ private set;
3635+ }
3636+
3637+ public string Driver {
3638+ get;
3639+ private set;
3640+ }
3641+
3642+ public UInt32 Index {
3643+ get { return info.index; }
3644+ }
3645+ public UInt32 OwnerModule {
3646+ get { return info.owner_module; }
3647+ }
3648+ public UInt32 Client {
3649+ get { return info.client; }
3650+ }
3651+ public UInt32 Sink {
3652+ get { return info.sink; }
3653+ }
3654+ public SampleSpec Spec {
3655+ get { return info.sample_spec; }
3656+ }
3657+ public ChannelMap Map {
3658+ get { return info.channel_map; }
3659+ }
3660+ public Volume Volume {
3661+ get { return info.volume.Copy (); }
3662+ }
3663+ public UInt64 BufferLatency {
3664+ get { return info.buffer_usec; }
3665+ }
3666+ public UInt64 SinkLatency {
3667+ get { return info.sink_usec; }
3668+ }
3669+ public bool Mute {
3670+ get { return (info.mute > 0); }
3671+ }
3672+ public PropList Properties {
3673+ get;
3674+ private set;
3675+ }
3676+
3677+ public Operation SetVolume (Volume v, Context.OperationSuccessCallback cb)
3678+ {
3679+ return ctx.SetSinkInputVolume (Index, v, cb);
3680+ }
3681+
3682+ public class VolumeChangedEventArgs : EventArgs
3683+ {
3684+ public VolumeChangedEventArgs (Volume vol)
3685+ {
3686+ newVolume = vol;
3687+ }
3688+
3689+ public Volume newVolume { get; private set; }
3690+ }
3691+
3692+ public event EventHandler<VolumeChangedEventArgs> VolumeChanged;
3693+
3694+ private void HandleSinkInputEvent (object sender, ServerEventArgs args)
3695+ {
3696+ Context c = sender as Context;
3697+ if (args.Type == EventType.Changed && args.index == Index && c != null) {
3698+ Operation o = ctx.GetSinkInputInfoByIndex (Index, (NativeSinkInputInfo i, int eol) => {
3699+ if (eol == 0) {
3700+ UpdateInfo (i);
3701+ }
3702+ });
3703+ o.Dispose ();
3704+ }
3705+ }
3706+
3707+ private void UpdateInfo (NativeSinkInputInfo info)
3708+ {
3709+ if (this.info.volume != info.volume || this.info.mute != info.mute) {
3710+ this.info = info;
3711+ ResampleMethod = Marshal.PtrToStringAnsi (info.resample_method);
3712+ PropList temp = new PropList (info.prop_handle);
3713+ Properties = temp.Copy ();
3714+ EventHandler<VolumeChangedEventArgs> handler;
3715+ handler = VolumeChanged;
3716+ if (handler != null) {
3717+ handler (this, new VolumeChangedEventArgs (info.volume));
3718+ }
3719+ } else {
3720+ this.info = info;
3721+ ResampleMethod = Marshal.PtrToStringAnsi (info.resample_method);
3722+ PropList temp = new PropList (info.prop_handle);
3723+ Properties = temp.Copy ();
3724+ }
3725+ }
3726+ }
3727+}
3728
3729=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs'
3730--- StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs 1970-01-01 00:00:00 +0000
3731+++ StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs 2012-02-04 07:53:22 +0000
3732@@ -0,0 +1,84 @@
3733+//
3734+// UnmanagedCallbackManager.cs
3735+//
3736+// Author:
3737+// Christopher James Halse Rogers <raof@ubuntu.com>
3738+//
3739+// Copyright © 2010 Christopher James Halse Rogers <raof@ubuntu.com>
3740+//
3741+// This program is free software: you can redistribute it and/or modify
3742+// it under the terms of the GNU Lesser General Public License as published by
3743+// the Free Software Foundation, either version 3 of the License, or
3744+// (at your option) any later version.
3745+//
3746+// This program is distributed in the hope that it will be useful,
3747+// but WITHOUT ANY WARRANTY; without even the implied warranty of
3748+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3749+// GNU Lesser General Public License for more details.
3750+//
3751+// You should have received a copy of the GNU Lesser General Public License
3752+// along with this program. If not, see <http://www.gnu.org/licenses/>.
3753+
3754+using System;
3755+using System.Linq;
3756+using System.Collections.Generic;
3757+
3758+namespace Pulseaudio
3759+{
3760+ internal class UnmanagedCallbackManager
3761+ {
3762+ private Dictionary<int, Action> delegates;
3763+ private List<int> pendingCookies;
3764+
3765+ public UnmanagedCallbackManager ()
3766+ {
3767+ delegates = new Dictionary<int, Action> ();
3768+ pendingCookies = new List<int> ();
3769+ }
3770+
3771+ public void AddDelegate (Action act, int cookie)
3772+ {
3773+ if (pendingCookies.Contains (cookie)) {
3774+ delegates[cookie] = act;
3775+ pendingCookies.Remove (cookie);
3776+ } else {
3777+ throw new Exception ();
3778+ }
3779+ }
3780+
3781+ public void AddDelegate<T1, T2, T3, T4> (Action<T1, T2, T3, T4> act, int cookie)
3782+ {
3783+ AddDelegate (() => act(default(T1), default(T2), default(T3), default(T4)), cookie);
3784+ }
3785+
3786+ public void AddDelegate<T1, T2, T3> (Action<T1, T2, T3> act, int cookie)
3787+ {
3788+ AddDelegate (() => act(default (T1), default (T2), default (T3)), cookie);
3789+ }
3790+
3791+ public void RemoveDelegate (int cookie)
3792+ {
3793+ if (!delegates.ContainsKey (cookie)) {
3794+ throw new Exception ();
3795+ }
3796+ delegates.Remove (cookie);
3797+ }
3798+
3799+ public int PendingCallbackCount {
3800+ get {
3801+ return delegates.Count ();
3802+ }
3803+ }
3804+
3805+ public int NewCookie ()
3806+ {
3807+ int nextCookie, maxDelegateCookie, maxPendingCookie;
3808+ maxDelegateCookie = delegates.Count () == 0 ? 0 : delegates.Keys.Max ();
3809+ maxPendingCookie = pendingCookies.Count () == 0 ? 0 : pendingCookies.Max ();
3810+
3811+ nextCookie = Math.Max (maxDelegateCookie, maxPendingCookie) + 1;
3812+ pendingCookies.Add (nextCookie);
3813+ return nextCookie;
3814+ }
3815+ }
3816+}
3817
3818=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs'
3819--- StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs 1970-01-01 00:00:00 +0000
3820+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs 2012-02-04 07:53:22 +0000
3821@@ -0,0 +1,184 @@
3822+//
3823+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3824+//
3825+// Util.cs is a part of Pulseaudio#
3826+//
3827+// Pulseaudio# is free software: you can redistribute it and/or modify
3828+// it under the terms of the GNU Lesser General Public License as published by
3829+// the Free Software Foundation, either version 3 of the License, or
3830+// (at your option) any later version.
3831+//
3832+// Pulseaudio# is distributed in the hope that it will be useful,
3833+// but WITHOUT ANY WARRANTY; without even the implied warranty of
3834+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3835+// GNU Lesser General Public License for more details.
3836+//
3837+// You should have received a copy of the GNU Lesser General Public License
3838+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
3839+//
3840+
3841+using System;
3842+using System.Runtime.InteropServices;
3843+
3844+namespace Pulseaudio
3845+{
3846+ public enum ErrorCode {
3847+ OK = 0, /**< No error */
3848+ AccessFailure, /**< Access failure */
3849+ UnknownCommand, /**< Unknown command */
3850+ InvalidArgument, /**< Invalid argument */
3851+ EntityExists, /**< Entity exists */
3852+ NoSuchEntity, /**< No such entity */
3853+ ConnectionRefused, /**< Connection refused */
3854+ Protocol, /**< Protocol error */
3855+ Timeout, /**< Timeout */
3856+ NoAuthKey, /**< No authorization key */
3857+ Internal, /**< Internal error */
3858+ ConnectionTerminated, /**< Connection terminated */
3859+ Killed, /**< Entity killed */
3860+ InvalidServer, /**< Invalid server */
3861+ ModuleInitFailed, /**< Module initialization failed */
3862+ BadState, /**< Bad state */
3863+ NoData, /**< No data */
3864+ Version, /**< Incompatible protocol version */
3865+ TooLarge, /**< Data too large */
3866+ NotSupported, /**< Operation not supported \since 0.9.5 */
3867+ Unknown, /**< The error code was unknown to the client */
3868+ NoExtension, /**< Extension does not exist. \since 0.9.12 */
3869+ Obsolete, /**< Obsolete functionality. \since 0.9.15 */
3870+ NotImplemented, /**< Missing implementation. \since 0.9.15 */
3871+ Forked, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */
3872+ IO, /**< An IO error happened. \since 0.9.16 */
3873+ PA_ERR_MAX /**< Not really an error but the first invalid error code */
3874+ }
3875+
3876+ public class Error
3877+ {
3878+ public enum Code {
3879+ OK = 0, /**< No error */
3880+ AccessFailure, /**< Access failure */
3881+ UnknownCommand, /**< Unknown command */
3882+ InvalidArgument, /**< Invalid argument */
3883+ EntityExists, /**< Entity exists */
3884+ NoSuchEntity, /**< No such entity */
3885+ ConnectionRefused, /**< Connection refused */
3886+ Protocol, /**< Protocol error */
3887+ Timeout, /**< Timeout */
3888+ NoAuthKey, /**< No authorization key */
3889+ Internal, /**< Internal error */
3890+ ConnectionTerminated, /**< Connection terminated */
3891+ Killed, /**< Entity killed */
3892+ InvalidServer, /**< Invalid server */
3893+ ModuleInitFailed, /**< Module initialization failed */
3894+ BadState, /**< Bad state */
3895+ NoData, /**< No data */
3896+ Version, /**< Incompatible protocol version */
3897+ TooLarge, /**< Data too large */
3898+ NotSupported, /**< Operation not supported \since 0.9.5 */
3899+ Unknown, /**< The error code was unknown to the client */
3900+ NoExtension, /**< Extension does not exist. \since 0.9.12 */
3901+ Obsolete, /**< Obsolete functionality. \since 0.9.15 */
3902+ NotImplemented, /**< Missing implementation. \since 0.9.15 */
3903+ Forked, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */
3904+ IO, /**< An IO error happened. \since 0.9.16 */
3905+ PA_ERR_MAX /**< Not really an error but the first invalid error code */
3906+ }
3907+
3908+ private Code error;
3909+ private string error_string;
3910+
3911+ internal Error (Code error)
3912+ {
3913+ this.error = error;
3914+ }
3915+
3916+ public Code ErrorCode {
3917+ get {
3918+ return error;
3919+ }
3920+ }
3921+
3922+ public string Message {
3923+ get {
3924+ if (string.IsNullOrEmpty (error_string)) {
3925+ error_string = Marshal.PtrToStringAnsi (pa_strerror (Convert.ToInt32 (error)));
3926+ }
3927+ return error_string;
3928+ }
3929+ }
3930+
3931+ public override string ToString ()
3932+ {
3933+ return string.Format("[Error: {0}]", Message);
3934+ }
3935+
3936+ public override bool Equals (object obj)
3937+ {
3938+ Error other = obj as Error;
3939+ if (other != null) {
3940+ return (error == other.error);
3941+ }
3942+ if (obj is Code) {
3943+ return error == (Code)obj;
3944+ }
3945+ return base.Equals (obj);
3946+ }
3947+
3948+ public bool Equals (Error b)
3949+ {
3950+ return error == b.error;
3951+ }
3952+
3953+ public override int GetHashCode ()
3954+ {
3955+ return error.GetHashCode ();
3956+ }
3957+
3958+ public static bool operator==(Error a, Code b)
3959+ {
3960+ return a.error == b;
3961+ }
3962+
3963+ public static bool operator!=(Error a, Code b)
3964+ {
3965+ return !(a==b);
3966+ }
3967+
3968+ public static bool operator==(Code a, Error b)
3969+ {
3970+ return b == a;
3971+ }
3972+
3973+ public static bool operator!=(Code a, Error b)
3974+ {
3975+ return !(a==b);
3976+ }
3977+
3978+ public static bool operator==(Error a, Error b)
3979+ {
3980+ if (Object.ReferenceEquals (a, b)) {
3981+ return true;
3982+ }
3983+ return a.error == b.error;
3984+ }
3985+
3986+ public static bool operator!=(Error a, Error b)
3987+ {
3988+ return !(a==b);
3989+ }
3990+
3991+ [DllImport ("pulse")]
3992+ private static extern IntPtr pa_strerror (int error);
3993+ }
3994+
3995+ public static class Util
3996+ {
3997+ public static string ErrorStringFromErrno (ErrorCode error)
3998+ {
3999+ return Marshal.PtrToStringAnsi (pa_strerror (Convert.ToInt32 (error)));
4000+ }
4001+
4002+ [DllImport ("pulse")]
4003+ private static extern IntPtr pa_strerror (int error);
4004+ }
4005+}
4006
4007=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs'
4008--- StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs 1970-01-01 00:00:00 +0000
4009+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs 2012-02-04 07:53:22 +0000
4010@@ -0,0 +1,137 @@
4011+//
4012+// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
4013+//
4014+// Volume.cs is a part of Pulseaudio#
4015+//
4016+// Pulseaudio# is free software: you can redistribute it and/or modify
4017+// it under the terms of the GNU Lesser General Public License as published by
4018+// the Free Software Foundation, either version 3 of the License, or
4019+// (at your option) any later version.
4020+//
4021+// Pulseaudio# is distributed in the hope that it will be useful,
4022+// but WITHOUT ANY WARRANTY; without even the implied warranty of
4023+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4024+// GNU Lesser General Public License for more details.
4025+//
4026+// You should have received a copy of the GNU Lesser General Public License
4027+// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
4028+//
4029+
4030+using System;
4031+using System.Text;
4032+using System.Runtime.InteropServices;
4033+
4034+namespace Pulseaudio
4035+{
4036+ public class Constants
4037+ {
4038+ public const int MaxChannels = 32;
4039+ }
4040+
4041+ [StructLayout (LayoutKind.Sequential)]
4042+ public class Volume
4043+ {
4044+ private Byte channels;
4045+ [MarshalAs (UnmanagedType.ByValArray, SizeConst=Constants.MaxChannels)]
4046+ private UInt32 [] values;
4047+
4048+ private static readonly UInt32 norm = 0x10000U;
4049+ private static readonly UInt32 mute = 0;
4050+
4051+ public Volume()
4052+ {
4053+ values = new uint[Constants.MaxChannels];
4054+ pa_cvolume_init (this);
4055+ channels = 2;
4056+ }
4057+
4058+ /// <summary>
4059+ /// Reset the volume of all channels to no change (100%)
4060+ /// </summary>
4061+ public void Reset ()
4062+ {
4063+ pa_cvolume_set (this, channels, norm);
4064+ }
4065+
4066+ /// <summary>
4067+ /// Set the volume of all channels.
4068+ /// </summary>
4069+ /// <param name="val">
4070+ /// A <see cref="System.Double"/>. The nominal range is 0 - 1.0, with 0 corresponding to mute
4071+ /// and 1.0 corresponding to full volume (or, equivalently, no volume adjustment).
4072+ /// It is possible to set values > 1.0. This is not an error, but is not generally a good idea.
4073+ /// </param>
4074+ public void Set (double val)
4075+ {
4076+ pa_cvolume_set (this, channels, (UInt32)(val * norm));
4077+ }
4078+
4079+ public Volume Copy ()
4080+ {
4081+ Volume retVal = new Volume ();
4082+ retVal.channels = channels;
4083+ values.CopyTo (retVal.values, 0);
4084+ return retVal;
4085+ }
4086+
4087+ public override bool Equals (object obj)
4088+ {
4089+ if (obj is Volume) {
4090+ return (this as Volume) == (obj as Volume);
4091+ }
4092+ return false;
4093+ }
4094+
4095+ public override int GetHashCode ()
4096+ {
4097+ return base.GetHashCode ();
4098+ }
4099+
4100+ public static bool operator== (Volume a, Volume b)
4101+ {
4102+ return pa_cvolume_equal (a, b) != 0;
4103+ }
4104+ public static bool operator!= (Volume a, Volume b)
4105+ {
4106+ return !(a==b);
4107+ }
4108+
4109+ public override string ToString ()
4110+ {
4111+ StringBuilder buffer = new StringBuilder (PA_CVOLUME_SNPRINT_BUFFER_MAX);
4112+ pa_cvolume_snprint (buffer, new IntPtr (buffer.Capacity), this);
4113+ return buffer.ToString ();
4114+ }
4115+
4116+ public double ToScalarAvg ()
4117+ {
4118+ UInt32 rawVol = pa_cvolume_avg (this);
4119+ return ((((double)rawVol) - mute) / norm);
4120+ }
4121+
4122+ public void Modify (double amount)
4123+ {
4124+ if (amount > 0) {
4125+ pa_cvolume_inc (this, (UInt32)((amount - mute) * norm));
4126+ } else {
4127+ pa_cvolume_dec (this, (UInt32)((-amount - mute) * norm));
4128+ }
4129+ }
4130+
4131+ [DllImport ("pulse")]
4132+ private static extern int pa_cvolume_equal (Volume a, Volume b);
4133+ [DllImport ("pulse")]
4134+ private static extern void pa_cvolume_init ([Out] Volume a);
4135+ [DllImport ("pulse")]
4136+ private static extern void pa_cvolume_set ([In, Out] Volume vol, uint channels, UInt32 val);
4137+ [DllImport ("pulse")]
4138+ private static extern void pa_cvolume_snprint (System.Text.StringBuilder buffer, IntPtr bufferSize, Volume vol);
4139+ static readonly int PA_CVOLUME_SNPRINT_BUFFER_MAX = 320;
4140+ [DllImport ("pulse")]
4141+ private static extern UInt32 pa_cvolume_avg (Volume vol);
4142+ [DllImport ("pulse")]
4143+ private static extern void pa_cvolume_inc ([In, Out]Volume vol, UInt32 amount);
4144+ [DllImport ("pulse")]
4145+ private static extern void pa_cvolume_dec ([In, Out]Volume vol, UInt32 amount);
4146+ }
4147+}
4148
4149=== added file 'StandardPlugins/Sound/src/SoundDockItem.cs'
4150--- StandardPlugins/Sound/src/SoundDockItem.cs 1970-01-01 00:00:00 +0000
4151+++ StandardPlugins/Sound/src/SoundDockItem.cs 2012-02-04 07:53:22 +0000
4152@@ -0,0 +1,537 @@
4153+//
4154+// Copyright (C) 2011 Rico Tzschichholz
4155+//
4156+// This program is free software: you can redistribute it and/or modify
4157+// it under the terms of the GNU General Public License as published by
4158+// the Free Software Foundation, either version 3 of the License, or
4159+// (at your option) any later version.
4160+//
4161+// This program is distributed in the hope that it will be useful,
4162+// but WITHOUT ANY WARRANTY; without even the implied warranty of
4163+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4164+// GNU General Public License for more details.
4165+//
4166+// You should have received a copy of the GNU General Public License
4167+// along with this program. If not, see <http://www.gnu.org/licenses/>.
4168+//
4169+
4170+using System;
4171+using System.Collections.Generic;
4172+using System.Collections.ObjectModel;
4173+using System.Linq;
4174+using System.Text;
4175+using System.Threading;
4176+
4177+using Mono.Unix;
4178+
4179+using Cairo;
4180+using GLib;
4181+using Gdk;
4182+using Wnck;
4183+
4184+using Docky.Items;
4185+using Docky.Menus;
4186+using Docky.Services;
4187+using Docky.Services.Prefs;
4188+
4189+using Pulseaudio;
4190+using Pulseaudio.GLib;
4191+
4192+namespace Sound
4193+{
4194+ public class SoundDockItem : IconDockItem
4195+ {
4196+ static IPreferences prefs = DockServices.Preferences.Get <SoundDockItem> ();
4197+
4198+ object update_lock = new object ();
4199+
4200+ const string AlsaCommandVolumeDown = "amixer set Master 3%- /dev/null";
4201+ const string AlsaCommandVolumeUp = "amixer set Master 3%+ /dev/null";
4202+
4203+ Pulseaudio.Context pa_context;
4204+
4205+ List<SinkInput> PASinkInputs = new List<SinkInput> ();
4206+ public List<SinkInput> UsableSinkInputs {
4207+ get {
4208+ if (CurrentSink == null)
4209+ return new List<SinkInput> ();
4210+ return PASinkInputs.FindAll (si => si.Sink == CurrentSink.Index && UsableClients.Exists (c => c.Index == si.Client));
4211+ }
4212+ }
4213+
4214+ List<Client> PAClients = new List<Client> ();
4215+ public List<Client> UsableClients {
4216+ get {
4217+ if (CurrentSink == null)
4218+ return new List<Client> ();
4219+ return PAClients.FindAll (c => PASinkInputs.Exists (si => si.Sink == CurrentSink.Index && si.Client == c.Index));
4220+ }
4221+ }
4222+
4223+ List<Sink> PASinks = new List<Sink> ();
4224+ Sink current_sink;
4225+ public Sink CurrentSink {
4226+ get {
4227+ //TODO determine current user selected default sink
4228+ if (PASinks.Any () && (current_sink == null || !PASinks.Contains (current_sink))) {
4229+ int pref_index = prefs.Get<int> ("CurrentSinkIndex", 0);
4230+ current_sink = PASinks.Find (s => s.Index == pref_index);
4231+
4232+ if (current_sink == null)
4233+ current_sink = PASinks.DefaultIfEmpty (null).FirstOrDefault ();
4234+
4235+ if (current_sink != null) {
4236+ current_sink.VolumeChanged += HandlePulseaudioSinkVolumeChanged;
4237+ prefs.Set<int> ("CurrentSinkIndex", (int)current_sink.Index);
4238+ OnVolumeChanged ();
4239+ }
4240+ }
4241+ return current_sink;
4242+ }
4243+ private set {
4244+ if (current_sink != null)
4245+ current_sink.VolumeChanged -= HandlePulseaudioSinkVolumeChanged;
4246+
4247+ current_sink = value;
4248+
4249+ if (current_sink != null) {
4250+ current_sink.VolumeChanged += HandlePulseaudioSinkVolumeChanged;
4251+ prefs.Set<int> ("CurrentSinkIndex", (int)current_sink.Index);
4252+ }
4253+ OnVolumeChanged ();
4254+ }
4255+ }
4256+
4257+ bool pa_try_reconnect = false;
4258+
4259+ bool IsPulseAudioAvailable {
4260+ get {
4261+ if (pa_try_reconnect && pa_context.State == Pulseaudio.Context.ConnectionState.Failed) {
4262+ lock (update_lock) {
4263+ Log<SoundDockItem>.Debug ("Reconnecting to Pulseaudio");
4264+ CurrentSink = null;
4265+ pa_context.Ready -= HandlePulseaudioContextReady;
4266+ pa_context.Dispose ();
4267+ pa_context = new Pulseaudio.Context ("Docky's Sound Docklet");
4268+ pa_context.Ready += HandlePulseaudioContextReady;
4269+ pa_context.Connect ();
4270+ }
4271+ }
4272+ return pa_context != null && pa_context.State == Pulseaudio.Context.ConnectionState.Ready && CurrentSink != null;
4273+ }
4274+ }
4275+
4276+ public override string UniqueID ()
4277+ {
4278+ return "SoundControl";
4279+ }
4280+
4281+ public SoundDockItem ()
4282+ {
4283+ HoverText = Catalog.GetString ("Volume Control");
4284+ Icon = "preferences-desktop-sound";
4285+
4286+ Log<SoundDockItem>.Debug ("Connecting to Pulseaudio");
4287+ pa_context = new Pulseaudio.Context ("Docky's Sound Docklet");
4288+ pa_context.Ready += HandlePulseaudioContextReady;
4289+ pa_context.Connect ();
4290+ }
4291+
4292+ void HandlePulseaudioContextReady ()
4293+ {
4294+ Log<SoundDockItem>.Debug ("Using Pulseaudio (API {0})", pa_context.ServerAPI);
4295+
4296+ InitializeSinkList ();
4297+ InitializeClientList ();
4298+ InitializeSinkInputList ();
4299+
4300+ pa_context.SinkEvent += HandlePulseaudioContextSinkEvent;
4301+ pa_context.SinkInputEvent += HandlePulseaudioContextSinkInputEvent;
4302+ pa_context.ClientEvent += HandlePulseaudioContextClientEvent;
4303+
4304+ // if we managed to connect once, we will retry if the connection gets lost
4305+ pa_try_reconnect = true;
4306+ }
4307+
4308+ void InitializeClientList ()
4309+ {
4310+ lock (PAClients) {
4311+ PAClients.ForEach (c => c.Dispose ());
4312+ PAClients.Clear ();
4313+ using (Operation o = pa_context.EnumerateClients ((Client client) => {
4314+ Log<SoundDockItem>.Debug ("Found Client \"{1}\" ({0})", client.Index, client.Name);
4315+ PAClients.Add (client);
4316+ }))
4317+ {
4318+ o.Wait ();
4319+ }
4320+ }
4321+ }
4322+
4323+ void HandlePulseaudioContextClientEvent (object sender, ServerEventArgs e)
4324+ {
4325+ lock (PAClients) {
4326+ if (e.Type == Pulseaudio.EventType.Removed) {
4327+ Client c = PAClients.Find (cl => cl.Index == e.index);
4328+ if (c != null) {
4329+ Log<SoundDockItem>.Debug ("Remove Client \"{1}\" ({0})", c.Index, c.Name);
4330+ PAClients.Remove (c);
4331+ c.Dispose ();
4332+ }
4333+ } else if (e.Type == Pulseaudio.EventType.Added) {
4334+ //TODO use index directly to get this particular Client
4335+ using (Operation o = pa_context.EnumerateClients ((Client client) => {
4336+ if (e.index == client.Index) {
4337+ Log<SoundDockItem>.Debug ("Add Client \"{1}\" ({0})", client.Index, client.Name);
4338+ PAClients.Add (client);
4339+ }
4340+ }))
4341+ {
4342+ o.Wait ();
4343+ }
4344+ }
4345+ }
4346+ }
4347+
4348+ void InitializeSinkInputList ()
4349+ {
4350+ lock (PASinkInputs) {
4351+ PASinkInputs.ForEach (si => si.Dispose ());
4352+ PASinkInputs.Clear ();
4353+ using (Operation o = pa_context.EnumerateSinkInputs ((SinkInput sinkinput, int eol) => {
4354+ if (eol == 0) {
4355+ Log<SoundDockItem>.Debug ("Found SinkInput {0} for Client {1} \"{2}\"", sinkinput.Index, sinkinput.Client, sinkinput.Name);
4356+ PASinkInputs.Add (sinkinput);
4357+ }
4358+ }))
4359+ {
4360+ o.Wait ();
4361+ }
4362+ }
4363+ }
4364+
4365+ void HandlePulseaudioContextSinkInputEvent (object sender, ServerEventArgs e)
4366+ {
4367+ lock (PASinkInputs) {
4368+ if (e.Type == Pulseaudio.EventType.Removed) {
4369+ SinkInput si = PASinkInputs.Find (sii => sii.Index == e.index);
4370+ if (si != null) {
4371+ Log<SoundDockItem>.Debug ("Remove SinkInput {0} for Client {1} \"{2}\"", si.Index, si.Client, si.Name);
4372+ PASinkInputs.Remove (si);
4373+ si.Dispose ();
4374+ }
4375+ } else if (e.Type == Pulseaudio.EventType.Added) {
4376+ //TODO use index directly to get this particular SinkInput
4377+ using (Operation o = pa_context.EnumerateSinkInputs ((SinkInput sinkinput, int eol) => {
4378+ if (eol == 0 && e.index == sinkinput.Index) {
4379+ Log<SoundDockItem>.Debug ("Add SinkInput {0} for Client {1} \"{2}\"", sinkinput.Index, sinkinput.Client, sinkinput.Name);
4380+ PASinkInputs.Add (sinkinput);
4381+ }
4382+ }))
4383+ {
4384+ o.Wait ();
4385+ }
4386+ }
4387+ }
4388+ }
4389+
4390+ void InitializeSinkList ()
4391+ {
4392+ lock (PASinks) {
4393+ PASinks.ForEach (s => {
4394+ s.VolumeChanged -= HandlePulseaudioSinkVolumeChanged;
4395+ s.Dispose ();
4396+ });
4397+ PASinks.Clear ();
4398+ using (Operation o = pa_context.EnumerateSinks ((Sink sink) => {
4399+ Log<SoundDockItem>.Debug ("Found Device \"{1}\" ({0})", sink.Index, sink.Description);
4400+ PASinks.Add (sink);
4401+ //OnVolumeChanged ();
4402+ }))
4403+ {
4404+ o.Wait ();
4405+ }
4406+ }
4407+ }
4408+
4409+ void HandlePulseaudioContextSinkEvent (object sender, ServerEventArgs e)
4410+ {
4411+ lock (PASinks) {
4412+ if (e.Type == Pulseaudio.EventType.Removed) {
4413+ Sink s = PASinks.Find (si => si.Index == e.index);
4414+ if (s != null) {
4415+ Log<SoundDockItem>.Debug ("Remove Device \"{1}\" ({0})", s.Index, s.Description);
4416+ PASinks.Remove (s);
4417+ s.Dispose ();
4418+ }
4419+ } else if (e.Type == Pulseaudio.EventType.Added) {
4420+ //TODO use index to get this particular Sink
4421+ using (Operation o = pa_context.EnumerateSinks ((Sink sink) => {
4422+ if (e.index == sink.Index) {
4423+ Log<SoundDockItem>.Debug ("Add Device \"{1}\" ({0})", sink.Index, sink.Description);
4424+ PASinks.Add (sink);
4425+ }
4426+ }))
4427+ {
4428+ o.Wait ();
4429+ }
4430+ }
4431+ }
4432+ }
4433+
4434+ void HandlePulseaudioSinkVolumeChanged (object sender, Sink.VolumeChangedEventArgs e)
4435+ {
4436+ OnVolumeChanged ();
4437+ }
4438+
4439+ void OnVolumeChanged ()
4440+ {
4441+ if (!IsPulseAudioAvailable) {
4442+ HoverText = Catalog.GetString ("Volume Control");
4443+ Icon = "preferences-desktop-sound";
4444+ return;
4445+ }
4446+
4447+ double volume = CurrentSink.Volume.ToScalarAvg ();
4448+ Icon = GetVolumeIcon (volume, CurrentSink.Mute);
4449+ HoverText = String.Format (Catalog.GetString ("Volume {0}%"), Math.Round (volume * 100))
4450+ + (CurrentSink.Mute ? " (" + Catalog.GetString ("Muted") + ")" : "");
4451+ }
4452+
4453+ void PulseaudioChangeVolume (double val)
4454+ {
4455+ if (!IsPulseAudioAvailable)
4456+ return;
4457+
4458+ Pulseaudio.Volume vol = CurrentSink.Volume.Copy ();
4459+ vol.Modify (val);
4460+
4461+ //TODO should we support 150% ?
4462+ if (vol.ToScalarAvg () < 0)
4463+ vol.Set (0);
4464+ else if (vol.ToScalarAvg () > 1)
4465+ vol.Set (1);
4466+
4467+ CurrentSink.SetVolume (vol, (int success) => {});
4468+ }
4469+
4470+ void PulseaudioSetVolume (double val)
4471+ {
4472+ if (!IsPulseAudioAvailable)
4473+ return;
4474+
4475+ Pulseaudio.Volume vol = CurrentSink.Volume.Copy ();
4476+ vol.Set (val);
4477+
4478+ //TODO should we support 150% ?
4479+ if (vol.ToScalarAvg () < 0)
4480+ vol.Set (0);
4481+ else if (vol.ToScalarAvg () > 1)
4482+ vol.Set (1);
4483+
4484+ CurrentSink.SetVolume (vol, (int success) => {});
4485+ }
4486+
4487+ void PulseaudioSetSinkInputVolume (SinkInput sinkinput, double val)
4488+ {
4489+ if (!IsPulseAudioAvailable)
4490+ return;
4491+
4492+ Pulseaudio.Volume vol = sinkinput.Volume.Copy ();
4493+ vol.Set (val);
4494+
4495+ //TODO should we support 150% ?
4496+ if (vol.ToScalarAvg () < 0)
4497+ vol.Set (0);
4498+ else if (vol.ToScalarAvg () > 1)
4499+ vol.Set (1);
4500+
4501+ sinkinput.SetVolume (vol, (int success) => {});
4502+ }
4503+
4504+ string GetVolumeIcon (double volume, bool muted)
4505+ {
4506+ //audio-output-none-panel, audio-volume-high-panel, audio-volume-low-panel, audio-volume-low-zero-panel,
4507+ //audio-volume-medium-panel, audio-volume-muted-blocked-panel, audio-volume-muted-panel
4508+ if (!PASinks.Any ())
4509+ return "audio-output-none-panel;;audio-volume-muted";
4510+
4511+ if (muted)
4512+ return "audio-volume-muted-panel;;audio-volume-muted";
4513+ else if (volume <= 0.05)
4514+ return "audio-volume-low-zero-panel;;audio-volume-muted";
4515+ else if (volume < 0.2)
4516+ return "audio-volume-low-panel;;audio-volume-low";
4517+ else if (volume < 0.6)
4518+ return "audio-volume-medium-panel;;audio-volume-medium";
4519+ else
4520+ return "audio-volume-high-panel;;audio-volume-high";
4521+ }
4522+
4523+ protected override void OnScrolled (Gdk.ScrollDirection direction, Gdk.ModifierType mod)
4524+ {
4525+ if (IsPulseAudioAvailable) {
4526+ if (direction == Gdk.ScrollDirection.Up) {
4527+ PulseaudioChangeVolume (0.03);
4528+ } else if (direction == Gdk.ScrollDirection.Down) {
4529+ PulseaudioChangeVolume (-0.03);
4530+ }
4531+ } else {
4532+ if (direction == Gdk.ScrollDirection.Up) {
4533+ SimpleExecute (AlsaCommandVolumeUp);
4534+ } else if (direction == Gdk.ScrollDirection.Down) {
4535+ SimpleExecute (AlsaCommandVolumeDown);
4536+ }
4537+ }
4538+ }
4539+
4540+ public override MenuButton MenuButton {
4541+ get { return MenuButton.Left | MenuButton.Right; }
4542+ }
4543+
4544+ protected override ClickAnimation OnClicked (uint button, Gdk.ModifierType mod, double xPercent, double yPercent)
4545+ {
4546+ if (button == 1 && IsPulseAudioAvailable) {
4547+ //TODO mute and unmute all sinks
4548+ pa_context.SetSinkMute (CurrentSink.Index, !CurrentSink.Mute, (int success) => {});
4549+ }
4550+ return ClickAnimation.None;
4551+ }
4552+
4553+ protected override MenuList OnGetMenuItems ()
4554+ {
4555+ MenuList list = new MenuList ();
4556+
4557+ list[MenuListContainer.Header].Add (new MenuItem (Catalog.GetString ("Sound Preferences..."), "gnome-volume-control;;multimedia-volume-control", (o, a) => { DockServices.System.Execute ("gnome-volume-control"); }));
4558+
4559+ if (IsPulseAudioAvailable) {
4560+ // TODO only show them optionally
4561+ // show Output-Devices if there are more than one
4562+ if (PASinks.Count () > 1) {
4563+ list.SetContainerTitle (MenuListContainer.Actions, Catalog.GetString ("Available Outputs"));
4564+ foreach (Sink sink in PASinks) {
4565+ Sink s = sink;
4566+ MenuItem m;
4567+ if (CurrentSink == s)
4568+ m = new MenuItem (s.Description, "gtk-apply");
4569+ else
4570+ m = new MenuItem (s.Description, "", (o, a) => { CurrentSink = s; });
4571+ list[MenuListContainer.Actions].Add (m);
4572+ }
4573+ }
4574+
4575+ // add Client list
4576+ if (PAClients.Any ()) {
4577+ //list.SetContainerTitle (MenuListContainer.RelatedItems, Catalog.GetString ("Playing Applications"));
4578+ foreach (Client client in UsableClients.OrderBy (c => c.Name)) {
4579+ // get DesktopItem from WindowMatcher for this PID
4580+ //int pid = int.Parse (pl[Properties.ApplicationPID]);
4581+ //DesktopItem di = null;
4582+ //Wnck.Window w = null;
4583+ //foreach (Wnck.Window window in Wnck.Screen.Default.Windows)
4584+ // if (window.Pid == pid) {
4585+ //di = WindowMatcher.Default.DesktopItemForWindow (window);
4586+ // w = window;
4587+ // break;
4588+ // }
4589+
4590+ string icon = client.Properties[Properties.ApplicationIconName];
4591+ if (string.IsNullOrEmpty (icon))
4592+ icon = "applications-multimedia";
4593+
4594+ foreach (SinkInput sinkinput in UsableSinkInputs.FindAll (si => si.Client == client.Index).OrderBy (si => si.Name)) {
4595+ SinkInput si = sinkinput;
4596+ list[MenuListContainer.RelatedItems].Add (new SliderMenuItem (string.IsNullOrEmpty (si.Name) ? client.Name : string.Format ("{0} ({1})", client.Name, si.Name),
4597+ si.Volume.ToScalarAvg (), icon,
4598+ (o, e) => {
4599+ PulseaudioSetSinkInputVolume (si, e.NewPosition);
4600+ }));
4601+ }
4602+ }
4603+ }
4604+
4605+ // add master volume control
4606+ //TODO mute and unmute all sinks ?
4607+ if (CurrentSink != null) {
4608+ list[MenuListContainer.Footer].Add (new MenuItem ((CurrentSink.Mute ? Catalog.GetString ("Unmute") : Catalog.GetString ("Mute")),
4609+ "" ,
4610+ (o, a) => {
4611+ pa_context.SetSinkMute (CurrentSink.Index, !CurrentSink.Mute, (int success) => {});
4612+ }));
4613+ list[MenuListContainer.Footer].Add (new SliderMenuItem ("", CurrentSink.Volume.ToScalarAvg (),
4614+ GetVolumeIcon (CurrentSink.Volume.ToScalarAvg (), CurrentSink.Mute),
4615+ (o, e) => {
4616+ (o as MenuItem).Icon = GetVolumeIcon (e.NewPosition, (CurrentSink != null ? CurrentSink.Mute : true));
4617+ PulseaudioSetVolume (e.NewPosition);
4618+ }));
4619+ }
4620+ } else {
4621+ //TODO Alsa fallback
4622+ }
4623+
4624+ return list;
4625+ }
4626+
4627+ public void SimpleExecute (string executable)
4628+ {
4629+ try {
4630+ Log<SystemService>.Debug ("Calling: " + executable);
4631+ System.Diagnostics.Process proc = System.Diagnostics.Process.Start (executable);
4632+ proc.Dispose ();
4633+ } catch {
4634+ Log<SystemService>.Error ("Error executing '" + executable + "'");
4635+ }
4636+ }
4637+
4638+ #region Pulseaudio helper
4639+ public static void RunUntilEventSignal (Action action, EventWaitHandle until, string timeoutMessage)
4640+ {
4641+ var timeout = new EventWaitHandle (false, EventResetMode.AutoReset);
4642+ GLib.Timeout.Add (1000, () => {
4643+ timeout.Set ();
4644+ return false;
4645+ });
4646+ action ();
4647+ while (!until.WaitOne (0, true)) {
4648+ DrainEventLoop ();
4649+ if (timeout.WaitOne (0, true)) {
4650+ Log<SoundDockItem>.Error (timeoutMessage);
4651+ }
4652+ }
4653+ }
4654+
4655+ public static void DrainEventLoop ()
4656+ {
4657+ while (GLib.MainContext.Iteration (false)) {}
4658+ }
4659+ #endregion
4660+
4661+ #region IDisposable implementation
4662+ public override void Dispose ()
4663+ {
4664+ PAClients.ForEach (c => c.Dispose ());
4665+ PAClients.Clear ();
4666+
4667+ PASinkInputs.ForEach (si => si.Dispose ());
4668+ PASinkInputs.Clear ();
4669+
4670+ PASinks.ForEach (s => {
4671+ s.VolumeChanged -= HandlePulseaudioSinkVolumeChanged;
4672+ s.Dispose ();
4673+ });
4674+ PASinks.Clear ();
4675+
4676+ CurrentSink = null;
4677+
4678+ pa_context.Ready -= HandlePulseaudioContextReady;
4679+ pa_context.SinkEvent -= HandlePulseaudioContextSinkEvent;
4680+ pa_context.SinkInputEvent -= HandlePulseaudioContextSinkInputEvent;
4681+ pa_context.ClientEvent -= HandlePulseaudioContextClientEvent;
4682+ pa_context.Dispose (); // all handlers are getting removed
4683+
4684+ base.Dispose ();
4685+ }
4686+
4687+ #endregion
4688+ }
4689+}
4690
4691=== added file 'StandardPlugins/Sound/src/SoundItemProvider.cs'
4692--- StandardPlugins/Sound/src/SoundItemProvider.cs 1970-01-01 00:00:00 +0000
4693+++ StandardPlugins/Sound/src/SoundItemProvider.cs 2012-02-04 07:53:22 +0000
4694@@ -0,0 +1,42 @@
4695+//
4696+// Copyright (C) 2011 Rico Tzschichholz
4697+//
4698+// This program is free software: you can redistribute it and/or modify
4699+// it under the terms of the GNU General Public License as published by
4700+// the Free Software Foundation, either version 3 of the License, or
4701+// (at your option) any later version.
4702+//
4703+// This program is distributed in the hope that it will be useful,
4704+// but WITHOUT ANY WARRANTY; without even the implied warranty of
4705+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4706+// GNU General Public License for more details.
4707+//
4708+// You should have received a copy of the GNU General Public License
4709+// along with this program. If not, see <http://www.gnu.org/licenses/>.
4710+//
4711+
4712+using System;
4713+using System.Collections.Generic;
4714+
4715+using Docky.Items;
4716+
4717+namespace Sound
4718+{
4719+ public class SoundItemProvider : AbstractDockItemProvider
4720+ {
4721+ #region IDockItemProvider implementation
4722+
4723+ public override string Name {
4724+ get {
4725+ return "Sound";
4726+ }
4727+ }
4728+
4729+ #endregion
4730+
4731+ public SoundItemProvider ()
4732+ {
4733+ Items = new SoundDockItem ().AsSingle<AbstractDockItem> ();
4734+ }
4735+ }
4736+}
4737
4738=== modified file 'configure.ac'
4739--- configure.ac 2012-01-31 12:16:30 +0000
4740+++ configure.ac 2012-02-04 07:53:22 +0000
4741@@ -207,6 +207,8 @@
4742 StandardPlugins/RecentDocuments/Resources/RecentDocuments.addin.xml
4743 StandardPlugins/SessionManager/Makefile
4744 StandardPlugins/SessionManager/Resources/SessionManager.addin.xml
4745+StandardPlugins/Sound/Makefile
4746+StandardPlugins/Sound/Resources/Sound.addin.xml
4747 StandardPlugins/Timer/Makefile
4748 StandardPlugins/Timer/Resources/Timer.addin.xml
4749 StandardPlugins/Trash/Makefile
4750
4751=== modified file 'po/POTFILES.in'
4752--- po/POTFILES.in 2011-03-02 23:41:41 +0000
4753+++ po/POTFILES.in 2012-02-04 07:53:22 +0000
4754@@ -42,6 +42,7 @@
4755 StandardPlugins/NPR/src/StationDockItem.cs
4756 StandardPlugins/RecentDocuments/src/RecentFilesProvider.cs
4757 StandardPlugins/SessionManager/src/SessionActionsProvider.cs
4758+StandardPlugins/Sound/src/SoundDockItem.cs
4759 StandardPlugins/Timer/src/TimerDockItem.cs
4760 StandardPlugins/Timer/src/TimerMainDockItem.cs
4761 StandardPlugins/Trash/src/TrashDockItem.cs
4762
4763=== modified file 'po/docky.pot'
4764--- po/docky.pot 2011-06-17 11:50:40 +0000
4765+++ po/docky.pot 2012-02-04 07:53:22 +0000
4766@@ -1274,6 +1274,36 @@
4767 msgid "Are you sure you want to close all programs and shut down the computer?"
4768 msgstr ""
4769
4770+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:131
4771+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:290
4772+msgid "Volume Control"
4773+msgstr ""
4774+
4775+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:297
4776+#, csharp-format
4777+msgid "Volume {0}%"
4778+msgstr ""
4779+
4780+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:298
4781+msgid "Muted"
4782+msgstr ""
4783+
4784+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:405
4785+msgid "Sound Preferences..."
4786+msgstr ""
4787+
4788+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:411
4789+msgid "Available Outputs"
4790+msgstr ""
4791+
4792+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:452
4793+msgid "Unmute"
4794+msgstr ""
4795+
4796+#: ../StandardPlugins/Sound/src/SoundDockItem.cs:452
4797+msgid "Mute"
4798+msgstr ""
4799+
4800 #: ../StandardPlugins/Timer/src/TimerDockItem.cs:86
4801 #, csharp-format
4802 msgid "A timer set for {0} has expired."

Subscribers

People subscribed via source and target branches

to status/vote changes: