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
=== modified file 'Docky-2.sln'
--- Docky-2.sln 2011-12-22 15:47:35 +0000
+++ Docky-2.sln 2012-02-04 07:53:22 +0000
@@ -43,6 +43,8 @@
43EndProject43EndProject
44Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clippy", "StandardPlugins\Clippy\Clippy.csproj", "{E15414F1-C6F7-4D3E-8D25-BF32F93D3818}"44Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Clippy", "StandardPlugins\Clippy\Clippy.csproj", "{E15414F1-C6F7-4D3E-8D25-BF32F93D3818}"
45EndProject45EndProject
46Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sound", "StandardPlugins\Sound\Sound.csproj", "{16B2B0E3-3437-4F07-958D-E961E132F5F5}"
47EndProject
46Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkMonitor", "StandardPlugins\NetworkMonitor\NetworkMonitor.csproj", "{6FC9E311-1829-440F-9A6C-70302B656156}"48Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetworkMonitor", "StandardPlugins\NetworkMonitor\NetworkMonitor.csproj", "{6FC9E311-1829-440F-9A6C-70302B656156}"
47EndProject49EndProject
48Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docky.Widgets", "Docky.Widgets\Docky.Widgets.csproj", "{9CF2F475-7848-4333-9A35-93E8224653B3}"50Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Docky.Widgets", "Docky.Widgets\Docky.Widgets.csproj", "{9CF2F475-7848-4333-9A35-93E8224653B3}"
@@ -55,6 +57,10 @@
55 Release|Any CPU = Release|Any CPU57 Release|Any CPU = Release|Any CPU
56 EndGlobalSection58 EndGlobalSection
57 GlobalSection(ProjectConfigurationPlatforms) = postSolution59 GlobalSection(ProjectConfigurationPlatforms) = postSolution
60 {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61 {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
62 {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
63 {16B2B0E3-3437-4F07-958D-E961E132F5F5}.Release|Any CPU.Build.0 = Release|Any CPU
58 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU64 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Debug|Any CPU.Build.0 = Debug|Any CPU65 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Debug|Any CPU.Build.0 = Debug|Any CPU
60 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Release|Any CPU.ActiveCfg = Release|Any CPU66 {2F5FDD02-4BDA-4E80-8B92-A8BD55FFBC81}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -165,6 +171,7 @@
165 {AAD062AA-454A-4263-97E7-B6ED3020D84C} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}171 {AAD062AA-454A-4263-97E7-B6ED3020D84C} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
166 {66939AAB-0832-4D2D-8282-3E29AA772B69} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}172 {66939AAB-0832-4D2D-8282-3E29AA772B69} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
167 {E15414F1-C6F7-4D3E-8D25-BF32F93D3818} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}173 {E15414F1-C6F7-4D3E-8D25-BF32F93D3818} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
174 {16B2B0E3-3437-4F07-958D-E961E132F5F5} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
168 {6FC9E311-1829-440F-9A6C-70302B656156} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}175 {6FC9E311-1829-440F-9A6C-70302B656156} = {8D4EE2FA-9CF2-4890-B14A-1BA8752404FE}
169 EndGlobalSection176 EndGlobalSection
170 GlobalSection(MonoDevelopProperties) = preSolution177 GlobalSection(MonoDevelopProperties) = preSolution
171178
=== modified file 'Docky.CairoHelper/Cairo/Color_Extensions.cs'
--- Docky.CairoHelper/Cairo/Color_Extensions.cs 2009-12-06 05:14:01 +0000
+++ Docky.CairoHelper/Cairo/Color_Extensions.cs 2012-02-04 07:53:22 +0000
@@ -72,6 +72,11 @@
72 return new Cairo.Color (self.R, self.G, self.B, alpha);72 return new Cairo.Color (self.R, self.G, self.B, alpha);
73 }73 }
74 74
75 public static Cairo.Color Inverse (this Color self)
76 {
77 return new Cairo.Color (1.0 - self.R, 1.0 - self.G, 1.0 - self.B, self.A);
78 }
79
75 // Basic Getters80 // Basic Getters
76 public static double GetHue (this Color self)81 public static double GetHue (this Color self)
77 {82 {
7883
=== modified file 'Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs'
--- Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs 2011-03-13 16:47:29 +0000
+++ Docky.CairoHelper/Docky.CairoHelper/DockySurface.cs 2012-02-04 07:53:22 +0000
@@ -76,6 +76,41 @@
76 Internal = image;76 Internal = image;
77 }77 }
7878
79 DockySurface (Gdk.Pixbuf pixbuf, int width, int height, int offset_x, int offset_y)
80 {
81 Width = width;
82 Height = height;
83
84 Gdk.CairoHelper.SetSourcePixbuf (Context, pixbuf, offset_x, offset_y);
85 Context.Paint ();
86 }
87
88 public DockySurface (Gdk.Pixbuf pixbuf) : this(pixbuf, pixbuf.Width, pixbuf.Height, 0, 0)
89 {
90 }
91
92 public DockySurface (Gdk.Pixbuf pixbuf, bool square)
93 {
94 int width = pixbuf.Width;
95 int height = pixbuf.Height;
96 int offset_x = 0;
97 int offset_y = 0;
98
99 if (!square) {
100 Width = width;
101 Height = height;
102 } else {
103 int size = (width >= height ? width : height);
104 offset_x = (size - width) / 2;
105 offset_y = (size - height) / 2;
106 Width = size;
107 Height = size;
108 }
109
110 Gdk.CairoHelper.SetSourcePixbuf (Context, pixbuf, offset_x, offset_y);
111 Context.Paint ();
112 }
113
79 public void ResetContext ()114 public void ResetContext ()
80 {115 {
81 if (context == null)116 if (context == null)
82117
=== modified file 'Docky.Items/Docky.Items.csproj'
--- Docky.Items/Docky.Items.csproj 2011-12-22 15:47:35 +0000
+++ Docky.Items/Docky.Items.csproj 2012-02-04 07:53:22 +0000
@@ -98,6 +98,7 @@
98 <Compile Include="Docky.Menus\MenuList.cs" />98 <Compile Include="Docky.Menus\MenuList.cs" />
99 <Compile Include="Docky.Menus\IconMenuItem.cs" />99 <Compile Include="Docky.Menus\IconMenuItem.cs" />
100 <Compile Include="Docky.Items\IconEmblem.cs" />100 <Compile Include="Docky.Items\IconEmblem.cs" />
101 <Compile Include="Docky.Menus\SliderMenuItem.cs" />
101 </ItemGroup>102 </ItemGroup>
102 <ItemGroup>103 <ItemGroup>
103 <ProjectReference Include="..\Docky.Services\Docky.Services.csproj">104 <ProjectReference Include="..\Docky.Services\Docky.Services.csproj">
104105
=== added file 'Docky.Items/Docky.Menus/SliderMenuItem.cs'
--- Docky.Items/Docky.Menus/SliderMenuItem.cs 1970-01-01 00:00:00 +0000
+++ Docky.Items/Docky.Menus/SliderMenuItem.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,72 @@
1//
2// Copyright (C) 2011 Rico Tzschichholz
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17
18using System;
19
20namespace Docky.Menus
21{
22
23 public class PositionChangedEventArgs : EventArgs
24 {
25 public double NewPosition { get; private set; }
26
27 public PositionChangedEventArgs (double new_position)
28 {
29 NewPosition = new_position;
30 }
31 }
32
33 public class SliderMenuItem : MenuItem
34 {
35 public event EventHandler<PositionChangedEventArgs> PositionChanged;
36
37 double position;
38 public double Position {
39 get { return position; }
40 set {
41 if (position == value)
42 return;
43 position = value;
44 if (PositionChanged != null)
45 PositionChanged (this, new PositionChangedEventArgs (position));
46 }
47 }
48
49 public SliderMenuItem ()
50 {
51 }
52
53 public SliderMenuItem (string text, double position, string icon) : this (text, position, icon, false)
54 {
55 }
56
57 public SliderMenuItem (string text, double position, string icon, bool disabled) : base (text, icon, disabled)
58 {
59 this.position = position;
60 }
61
62 public SliderMenuItem (string text, double position, string icon, EventHandler<PositionChangedEventArgs> onSlide) : this(text, position, icon)
63 {
64 PositionChanged += onSlide;
65 }
66
67 public SliderMenuItem (string text, double position, string icon, EventHandler<PositionChangedEventArgs> onSlide, bool disabled) : this(text, position, icon, disabled)
68 {
69 PositionChanged += onSlide;
70 }
71 }
72}
073
=== modified file 'Docky.Items/Makefile.am'
--- Docky.Items/Makefile.am 2011-01-02 12:42:36 +0000
+++ Docky.Items/Makefile.am 2012-02-04 07:53:22 +0000
@@ -15,6 +15,7 @@
15 Docky.Menus/ProxyMenuItem.cs \15 Docky.Menus/ProxyMenuItem.cs \
16 Docky.Menus/MenuList.cs \16 Docky.Menus/MenuList.cs \
17 Docky.Menus/SeparatorMenuItem.cs \17 Docky.Menus/SeparatorMenuItem.cs \
18 Docky.Menus/SliderMenuItem.cs \
18 Docky.Menus/WindowMenuItem.cs \19 Docky.Menus/WindowMenuItem.cs \
19 Docky.Menus/IconMenuItem.cs \20 Docky.Menus/IconMenuItem.cs \
20 Docky.Items/WindowDockItem.cs \21 Docky.Items/WindowDockItem.cs \
2122
=== modified file 'Docky.Services/Docky.Services/DrawingService.cs'
--- Docky.Services/Docky.Services/DrawingService.cs 2011-01-16 10:10:00 +0000
+++ Docky.Services/Docky.Services/DrawingService.cs 2012-02-04 07:53:22 +0000
@@ -172,7 +172,23 @@
172 }172 }
173173
174 /// <summary>174 /// <summary>
175 /// Load an icon specifying only the size. Size will be used for both width and height.175 /// Load an icon at its native size. Note when this is used on icons that are not files or resources
176 /// an exception will be thrown.
177 /// </summary>
178 /// <param name="names">
179 /// A <see cref="System.String"/>
180 /// </param>
181 /// <returns>
182 /// A <see cref="Gdk.Pixbuf"/>
183 /// A <see cref="Docky.CairoHelper.DockySurface"/>
184 /// </returns>
185 public Gdk.Pixbuf LoadIcon (string names)
186 {
187 return LoadIcon (names, -1, -1);
188 }
189
190 /// <summary>
191 /// Load an icon specifying only the size. Size will be used for both width and height.
176 /// </summary>192 /// </summary>
177 /// <param name="names">193 /// <param name="names">
178 /// A <see cref="System.String"/>194 /// A <see cref="System.String"/>
@@ -182,6 +198,7 @@
182 /// </param>198 /// </param>
183 /// <returns>199 /// <returns>
184 /// A <see cref="Gdk.Pixbuf"/>200 /// A <see cref="Gdk.Pixbuf"/>
201 /// A <see cref="Docky.CairoHelper.DockySurface"/>
185 /// </returns>202 /// </returns>
186 public Gdk.Pixbuf LoadIcon (string names, int size)203 public Gdk.Pixbuf LoadIcon (string names, int size)
187 {204 {
@@ -189,21 +206,6 @@
189 }206 }
190 207
191 /// <summary>208 /// <summary>
192 /// Load an icon at its native size. Note when this is used on icons that are not files or resources
193 /// an exception will be thrown.
194 /// </summary>
195 /// <param name="names">
196 /// A <see cref="System.String"/>
197 /// </param>
198 /// <returns>
199 /// A <see cref="Gdk.Pixbuf"/>
200 /// </returns>
201 public Gdk.Pixbuf LoadIcon (string names)
202 {
203 return LoadIcon (names, -1);
204 }
205
206 /// <summary>
207 /// Returns the string name of the supplied icon.209 /// Returns the string name of the supplied icon.
208 /// </summary>210 /// </summary>
209 /// <param name="icon">211 /// <param name="icon">
210212
=== modified file 'Docky/Docky.csproj'
--- Docky/Docky.csproj 2011-12-22 15:47:35 +0000
+++ Docky/Docky.csproj 2012-02-04 07:53:22 +0000
@@ -80,6 +80,8 @@
80 <Compile Include="gtk-gui\Docky.ConfigurationWindow.cs" />80 <Compile Include="gtk-gui\Docky.ConfigurationWindow.cs" />
81 <Compile Include="Docky\DockletTile.cs" />81 <Compile Include="Docky\DockletTile.cs" />
82 <Compile Include="Mono\Options.cs" />82 <Compile Include="Mono\Options.cs" />
83 <Compile Include="Docky\Menus\SliderMenuItemWidget.cs" />
84 <Compile Include="Docky\Menus\TextMenuItemWidget.cs" />
83 </ItemGroup>85 </ItemGroup>
84 <ItemGroup>86 <ItemGroup>
85 <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">87 <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
8688
=== modified file 'Docky/Docky/Menus/DockItemMenu.cs'
--- Docky/Docky/Menus/DockItemMenu.cs 2010-10-15 15:32:36 +0000
+++ Docky/Docky/Menus/DockItemMenu.cs 2012-02-04 07:53:22 +0000
@@ -94,7 +94,7 @@
94 selected = NumberItems () - 1;94 selected = NumberItems () - 1;
95 95
96 UpdateSelected (selected);96 UpdateSelected (selected);
97 } while (GetSelectedItem ().item.Disabled);97 } while (GetSelectedItem ().MItem.Disabled);
98 } else if (evnt.Key == Gdk.Key.Down) {98 } else if (evnt.Key == Gdk.Key.Down) {
99 do {99 do {
100 int selected = GetSelected () + 1;100 int selected = GetSelected () + 1;
@@ -103,9 +103,9 @@
103 selected = 0;103 selected = 0;
104 104
105 UpdateSelected (selected);105 UpdateSelected (selected);
106 } while (GetSelectedItem ().item.Disabled);106 } while (GetSelectedItem ().MItem.Disabled);
107 } else if (evnt.Key == Gdk.Key.Return && GetSelectedItem () != null) {107 } else if (evnt.Key == Gdk.Key.Return && GetSelectedItem () != null) {
108 MenuItem item = (GetSelectedItem () as MenuItemWidget).item;108 MenuItem item = (GetSelectedItem () as MenuItemWidget).MItem;
109 if (!item.Disabled) {109 if (!item.Disabled) {
110 item.SendClick ();110 item.SendClick ();
111 Hide ();111 Hide ();
@@ -115,7 +115,7 @@
115 } else {115 } else {
116 foreach (Gtk.Widget widget in (Container.Child as VBox).Children)116 foreach (Gtk.Widget widget in (Container.Child as VBox).Children)
117 if (widget is MenuItemWidget) {117 if (widget is MenuItemWidget) {
118 MenuItem item = (widget as MenuItemWidget).item;118 MenuItem item = (widget as MenuItemWidget).MItem;
119 if (evnt.KeyValue == item.Mnemonic)119 if (evnt.KeyValue == item.Mnemonic)
120 if (!item.Disabled) {120 if (!item.Disabled) {
121 item.SendClick ();121 item.SendClick ();
@@ -177,16 +177,22 @@
177 177
178 first = false;178 first = false;
179 vbox.PackStart (widget);179 vbox.PackStart (widget);
180 } else {180 } else if (item is SliderMenuItem || item is MenuItem) {
181 MenuItemWidget menuItem = new MenuItemWidget (item);181 MenuItemWidget widget;
182 menuItem.SelectedChanged += HandleSelectedChanged;182
183 menuItem.TextColor = textColor;183 if (item is SliderMenuItem)
184 menuItem.MenuShowingIcons = hasIcon;184 widget = new SliderMenuItemWidget (item as SliderMenuItem);
185 else
186 widget = new TextMenuItemWidget (item);
187
188 widget.SelectedChanged += HandleSelectedChanged;
189 widget.TextColor = textColor;
190 widget.MenuShowingIcons = hasIcon;
185 191
186 first = false;192 first = false;
187 vbox.PackStart (menuItem, false, false, 0);193 vbox.PackStart (widget, false, false, 0);
188 194
189 width = Math.Max (width, menuItem.RequestedWidth);195 width = Math.Max (width, widget.RequestedWidth);
190 }196 }
191 }197 }
192 vbox.SetSizeRequest (width, -1);198 vbox.SetSizeRequest (width, -1);
193199
=== modified file 'Docky/Docky/Menus/MenuItemWidget.cs'
--- Docky/Docky/Menus/MenuItemWidget.cs 2011-02-08 07:28:57 +0000
+++ Docky/Docky/Menus/MenuItemWidget.cs 2012-02-04 07:53:22 +0000
@@ -1,6 +1,7 @@
1// 1//
2// Copyright (C) 2009 Jason Smith, Robert Dyer2// Copyright (C) 2009 Jason Smith, Robert Dyer
3// Copyright (C) 2010 Robert Dyer3// Copyright (C) 2010 Robert Dyer
4// Copyright (C) 2011 Rico Tzschichholz
4// 5//
5// This program is free software: you can redistribute it and/or modify6// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by7// it under the terms of the GNU General Public License as published by
@@ -28,22 +29,32 @@
2829
29namespace Docky.Menus30namespace Docky.Menus
30{31{
31 internal class MenuItemWidget : Gtk.EventBox32 internal abstract class MenuItemWidget : Gtk.EventBox
32 {33 {
33 const int MenuHeight = 22;34 public static MenuItemWidget NewFromMenuItem (MenuItem item)
34 const int MinWidth = 100;35 {
35 const int MaxWidth = 350;36 return new TextMenuItemWidget (item);
36 const int FontSize = 11;37 }
37 const int Padding = 4;38
38 const int IconBuffer = Padding - 1;39 public static MenuItemWidget NewFromMenuItem (SliderMenuItem item)
39 40 {
40 public MenuItem item;41 return new SliderMenuItemWidget (item);
42 }
43
44 protected const int MenuHeight = 22;
45 protected const int MinWidth = 100;
46 protected const int MaxWidth = 350;
47 protected const int FontSize = 11;
48 protected const int Padding = 4;
49 protected const int IconBuffer = Padding - 1;
50
51 public MenuItem MItem { get; protected set; }
41 52
42 public event EventHandler SelectedChanged;53 public event EventHandler SelectedChanged;
43 54
44 public bool Selected { get; set; }55 public bool Selected { get; set; }
45 56
46 bool menu_icons = false;57 protected bool menu_icons = false;
47 public bool MenuShowingIcons {58 public bool MenuShowingIcons {
48 get {59 get {
49 return menu_icons;60 return menu_icons;
@@ -58,18 +69,18 @@
58 69
59 public Cairo.Color TextColor { get; set; }70 public Cairo.Color TextColor { get; set; }
60 71
61 int TextWidth { get; set; }72 protected int TextWidth { get; set; }
62 public int RequestedWidth { get; protected set; }73 public int RequestedWidth { get; protected set; }
63 74
64 DockySurface icon_surface, emblem_surface;75 protected DockySurface icon_surface, emblem_surface;
65 76
66 internal MenuItemWidget (MenuItem item) : base()77 internal MenuItemWidget (MenuItem item) : base()
67 {78 {
68 TextColor = new Cairo.Color (1, 1, 1);79 TextColor = new Cairo.Color (1, 1, 1);
69 this.item = item;80 MItem = item;
70 item.IconChanged += ItemIconChanged;81 MItem.IconChanged += ItemIconChanged;
71 item.TextChanged += ItemTextChanged;82 MItem.TextChanged += ItemTextChanged;
72 item.DisabledChanged += ItemDisabledChanged;83 MItem.DisabledChanged += ItemDisabledChanged;
73 84
74 AddEvents ((int) Gdk.EventMask.AllEventsMask);85 AddEvents ((int) Gdk.EventMask.AllEventsMask);
75 86
@@ -77,10 +88,10 @@
77 VisibleWindow = false;88 VisibleWindow = false;
78 AboveChild = true;89 AboveChild = true;
79 90
80 CalcTextWidth ();91 SetSize ();
81 }92 }
82 93
83 void SetSize ()94 protected void SetSize ()
84 {95 {
85 RequestedWidth = TextWidth + 2 * Padding + 1;96 RequestedWidth = TextWidth + 2 * Padding + 1;
86 if (MenuShowingIcons)97 if (MenuShowingIcons)
@@ -89,45 +100,17 @@
89 SetSizeRequest (RequestedWidth, MenuHeight);100 SetSizeRequest (RequestedWidth, MenuHeight);
90 }101 }
91 102
92 void CalcTextWidth ()103 protected virtual void ItemDisabledChanged (object sender, EventArgs e)
93 {104 {
94 using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {105 QueueDraw ();
95 char accel;106 }
96 107
97 string text = GLib.Markup.EscapeText (item.Text.Replace ("\n", ""));108 protected virtual void ItemTextChanged (object sender, EventArgs e)
98 if (item.Mnemonic.HasValue)109 {
99 layout.SetMarkupWithAccel (text, '_', out accel);110 QueueDraw ();
100 else111 }
101 layout.SetMarkup (text);112
102 layout.FontDescription = Style.FontDescription;113 protected virtual void ItemIconChanged (object sender, EventArgs e)
103 layout.Ellipsize = Pango.EllipsizeMode.End;
104 layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
105 layout.FontDescription.Weight = Pango.Weight.Bold;
106
107 Pango.Rectangle logical, ink;
108 layout.GetPixelExtents (out ink, out logical);
109
110 TextWidth = Math.Min (MaxWidth, Math.Max (MinWidth, logical.Width));
111 HasTooltip = TextWidth < logical.Width;
112
113 layout.Context.Dispose ();
114 }
115
116 SetSize ();
117 }
118
119 void ItemDisabledChanged (object sender, EventArgs e)
120 {
121 QueueDraw ();
122 }
123
124 void ItemTextChanged (object sender, EventArgs e)
125 {
126 CalcTextWidth ();
127 QueueDraw ();
128 }
129
130 void ItemIconChanged (object sender, EventArgs e)
131 {114 {
132 if (icon_surface != null)115 if (icon_surface != null)
133 icon_surface.Dispose ();116 icon_surface.Dispose ();
@@ -136,19 +119,24 @@
136 QueueDraw ();119 QueueDraw ();
137 }120 }
138 121
122 protected void OnSelectedChanged ()
123 {
124 if (SelectedChanged != null)
125 SelectedChanged (this, EventArgs.Empty);
126 }
127
139 protected override bool OnButtonReleaseEvent (EventButton evnt)128 protected override bool OnButtonReleaseEvent (EventButton evnt)
140 {129 {
141 if (!item.Disabled)130 if (!MItem.Disabled)
142 item.SendClick ();131 MItem.SendClick ();
143 return item.Disabled;132 return MItem.Disabled;
144 }133 }
145 134
146 protected override bool OnMotionNotifyEvent (EventMotion evnt)135 protected override bool OnMotionNotifyEvent (EventMotion evnt)
147 {136 {
148 if (!item.Disabled && !Selected) {137 if (!MItem.Disabled && !Selected) {
149 Selected = true;138 Selected = true;
150 if (SelectedChanged != null)139 OnSelectedChanged ();
151 SelectedChanged (this, EventArgs.Empty);
152 QueueDraw ();140 QueueDraw ();
153 }141 }
154 return false;142 return false;
@@ -156,10 +144,9 @@
156 144
157 protected override bool OnEnterNotifyEvent (EventCrossing evnt)145 protected override bool OnEnterNotifyEvent (EventCrossing evnt)
158 {146 {
159 if (!item.Disabled && !Selected) {147 if (!MItem.Disabled && !Selected) {
160 Selected = true;148 Selected = true;
161 if (SelectedChanged != null)149 OnSelectedChanged ();
162 SelectedChanged (this, EventArgs.Empty);
163 QueueDraw ();150 QueueDraw ();
164 }151 }
165 return false;152 return false;
@@ -167,10 +154,9 @@
167154
168 protected override bool OnLeaveNotifyEvent (EventCrossing evnt)155 protected override bool OnLeaveNotifyEvent (EventCrossing evnt)
169 {156 {
170 if (!item.Disabled && Selected) {157 if (!MItem.Disabled && Selected) {
171 Selected = false;158 Selected = false;
172 if (SelectedChanged != null)159 OnSelectedChanged ();
173 SelectedChanged (this, EventArgs.Empty);
174 QueueDraw ();160 QueueDraw ();
175 }161 }
176 return base.OnLeaveNotifyEvent (evnt);162 return base.OnLeaveNotifyEvent (evnt);
@@ -178,32 +164,68 @@
178 164
179 protected override bool OnQueryTooltip (int x, int y, bool keyboard_tooltip, Tooltip tooltip)165 protected override bool OnQueryTooltip (int x, int y, bool keyboard_tooltip, Tooltip tooltip)
180 {166 {
181 tooltip.Text = item.Text;167 tooltip.Text = MItem.Text;
182 return true;168 return true;
183 }169 }
184 170
185 void PlaceSurface (Context cr, DockySurface surface, Gdk.Rectangle allocation)171 protected void DrawMenuItemIcon (Context cr)
186 {172 {
173 Gdk.Rectangle allocation = Allocation;
174
187 int iconSize = allocation.Height - IconBuffer * 2;175 int iconSize = allocation.Height - IconBuffer * 2;
188 176 int x = allocation.X + Padding;
189 int x = allocation.X + Padding + ((iconSize - surface.Width) / 2);177 int y = allocation.Y + IconBuffer;
190 int y = allocation.Y + IconBuffer + ((iconSize - surface.Height) / 2);178
191 179 if (icon_surface == null || (icon_surface.Height != iconSize && icon_surface.Width != iconSize)) {
192 cr.SetSource (surface.Internal, x, y);180 if (icon_surface != null)
181 icon_surface.Dispose ();
182 icon_surface = null;
183 if (MItem.ForcePixbuf == null)
184 icon_surface = LoadIcon (MItem.Icon, iconSize);
185 else
186 icon_surface = LoadIcon (MItem.ForcePixbuf, iconSize);
187 }
188 if (!string.IsNullOrEmpty (MItem.Emblem)
189 && (emblem_surface == null || (emblem_surface.Height != iconSize && emblem_surface.Width != iconSize))) {
190 if (emblem_surface != null)
191 emblem_surface.Dispose ();
192 emblem_surface = null;
193 if (!string.IsNullOrEmpty (MItem.Emblem))
194 emblem_surface = LoadIcon (MItem.Emblem, iconSize);
195 }
196
197 if (icon_surface == null)
198 return;
199
200 cr.Save ();
201
202 cr.SetSource (icon_surface.Internal, x, y);
203 cr.PaintWithAlpha (MItem.Disabled ? 0.5 : 1);
204
205 if (MItem.Bold) {
206 cr.Operator = Operator.Add;
207 cr.SetSource (icon_surface.Internal, x, y);
208 cr.PaintWithAlpha (.8);
209 cr.Operator = Operator.Over;
210 }
211
212 if (emblem_surface != null) {
213 cr.SetSource (emblem_surface.Internal, x, y);
214 cr.Paint ();
215 }
216
217 cr.Restore ();
193 }218 }
194 219
195 DockySurface LoadIcon (Pixbuf icon, int size)220 protected DockySurface LoadIcon (Gdk.Pixbuf pbuf, int size)
196 {221 {
197 DockySurface surface;222 Gdk.Pixbuf pixbuf = pbuf.Copy ().ARScale (size, size);
198 using (Gdk.Pixbuf pixbuf = icon.Copy ().ARScale (size, size)) {223 DockySurface surface = new DockySurface (pixbuf, true);
199 surface = new DockySurface (pixbuf.Width, pixbuf.Height);224 pixbuf.Dispose ();
200 Gdk.CairoHelper.SetSourcePixbuf (surface.Context, pixbuf, 0, 0);
201 surface.Context.Paint ();
202 }
203 return surface;225 return surface;
204 }226 }
205 227
206 DockySurface LoadIcon (string icon, int size)228 protected DockySurface LoadIcon (string icon, int size)
207 {229 {
208 bool monochrome = icon.StartsWith ("[monochrome]");230 bool monochrome = icon.StartsWith ("[monochrome]");
209 if (monochrome)231 if (monochrome)
@@ -225,85 +247,7 @@
225 return surface;247 return surface;
226 }248 }
227 249
228 protected override bool OnExposeEvent (EventExpose evnt)250 protected abstract override bool OnExposeEvent (EventExpose evnt);
229 {
230 if (!IsRealized)
231 return false;
232
233 Gdk.Rectangle allocation = Allocation;
234
235 int pixbufSize = allocation.Height - IconBuffer * 2;
236 if (item.ShowIcons && (icon_surface == null || (icon_surface.Height != pixbufSize && icon_surface.Width != pixbufSize))) {
237 if (icon_surface != null)
238 icon_surface.Dispose ();
239 if (emblem_surface != null)
240 emblem_surface.Dispose ();
241
242 if (item.ForcePixbuf == null)
243 icon_surface = LoadIcon (item.Icon, pixbufSize);
244 else
245 icon_surface = LoadIcon (item.ForcePixbuf, pixbufSize);
246
247 if (!string.IsNullOrEmpty (item.Emblem))
248 emblem_surface = LoadIcon (item.Emblem, pixbufSize);
249 }
250
251 using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
252 if (Selected && !item.Disabled) {
253 cr.Rectangle (allocation.X, allocation.Y, allocation.Width, allocation.Height);
254 cr.Color = TextColor.SetAlpha (.1);
255 cr.Fill ();
256 }
257
258 if (item.ShowIcons) {
259 PlaceSurface (cr, icon_surface, allocation);
260 cr.PaintWithAlpha (item.Disabled ? 0.5 : 1);
261
262 if (item.Bold) {
263 cr.Operator = Operator.Add;
264 PlaceSurface (cr, icon_surface, allocation);
265 cr.PaintWithAlpha (.8);
266 cr.Operator = Operator.Over;
267 }
268
269 if (!string.IsNullOrEmpty (item.Emblem)) {
270 PlaceSurface (cr, emblem_surface, allocation);
271 cr.Paint ();
272 }
273 }
274
275 using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
276 char accel;
277 string text = GLib.Markup.EscapeText (item.Text.Replace ("\n", ""));
278 if (item.Mnemonic.HasValue)
279 layout.SetMarkupWithAccel (text, '_', out accel);
280 else
281 layout.SetMarkup (text);
282 layout.Width = Pango.Units.FromPixels (TextWidth);
283 layout.FontDescription = Style.FontDescription;
284 layout.Ellipsize = Pango.EllipsizeMode.End;
285 layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
286 layout.FontDescription.Weight = Pango.Weight.Bold;
287
288 Pango.Rectangle logical, ink;
289 layout.GetPixelExtents (out ink, out logical);
290
291 int offset = Padding;
292 if (MenuShowingIcons)
293 offset += MenuHeight + Padding;
294 cr.MoveTo (allocation.X + offset, allocation.Y + (allocation.Height - logical.Height) / 2);
295 Pango.CairoHelper.LayoutPath (cr, layout);
296 cr.Color = TextColor.SetAlpha (item.Disabled ? 0.5 : 1);
297 cr.Fill ();
298
299 layout.Context.Dispose ();
300 }
301
302 (cr.Target as IDisposable).Dispose ();
303 }
304
305 return true;
306 }
307 251
308 public override void Dispose ()252 public override void Dispose ()
309 {253 {
@@ -315,10 +259,10 @@
315 emblem_surface.Dispose ();259 emblem_surface.Dispose ();
316 emblem_surface = null;260 emblem_surface = null;
317 261
318 item.IconChanged -= ItemIconChanged;262 MItem.IconChanged -= ItemIconChanged;
319 item.TextChanged -= ItemTextChanged;263 MItem.TextChanged -= ItemTextChanged;
320 item.DisabledChanged -= ItemDisabledChanged;264 MItem.DisabledChanged -= ItemDisabledChanged;
321 item = null;265 MItem = null;
322 266
323 base.Dispose ();267 base.Dispose ();
324 }268 }
325269
=== added file 'Docky/Docky/Menus/SliderMenuItemWidget.cs'
--- Docky/Docky/Menus/SliderMenuItemWidget.cs 1970-01-01 00:00:00 +0000
+++ Docky/Docky/Menus/SliderMenuItemWidget.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,224 @@
1//
2// Copyright (C) 2011 Rico Tzschichholz
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17
18using System;
19using System.Collections.Generic;
20
21using Cairo;
22using Gdk;
23using Gtk;
24
25using Docky.Services;
26using Docky.CairoHelper;
27
28namespace Docky.Menus
29{
30 internal class SliderMenuItemWidget : MenuItemWidget
31 {
32 const int Size = 16;
33 const double StepSize = 0.03;
34 const bool Highlight = true;
35
36 Gdk.Rectangle SliderArea { get; set; }
37 Gdk.Rectangle DragArea { get; set; }
38 bool DragActive { get; set; }
39
40 internal SliderMenuItemWidget (SliderMenuItem item) : base(item)
41 {
42 item.PositionChanged += ItemPositionChanged;
43 HasTooltip = !string.IsNullOrEmpty (item.Text);
44 SliderArea = Gdk.Rectangle.Zero;
45 DragArea = Gdk.Rectangle.Zero;
46 }
47
48 void SetPositionFromCursor (double x, double y)
49 {
50 (MItem as SliderMenuItem).Position =
51 Math.Max (0, Math.Min (1.0, 1.0 - (double) (SliderArea.X + SliderArea.Width - Size / 2 - x)
52 / (SliderArea.Width - Size)));
53 }
54
55 void ItemPositionChanged (object sender, PositionChangedEventArgs e)
56 {
57 QueueDraw ();
58 }
59
60 protected override void ItemTextChanged (object sender, EventArgs e)
61 {
62 HasTooltip = !string.IsNullOrEmpty (MItem.Text);
63 QueueDraw ();
64 }
65
66 protected override bool OnButtonPressEvent (EventButton evnt)
67 {
68 if (!SliderArea.Contains ((int) evnt.X, (int) evnt.Y))
69 return false;
70
71 if (DragArea.Contains ((int) evnt.X, (int) evnt.Y))
72 DragActive = true;
73
74 SetPositionFromCursor (evnt.X, evnt.Y);
75
76 return false;
77 }
78
79 protected override bool OnButtonReleaseEvent (EventButton evnt)
80 {
81 if (MItem.Disabled)
82 return false;
83
84 DragActive = false;
85
86 return true;
87 }
88
89 protected override bool OnMotionNotifyEvent (EventMotion evnt)
90 {
91 base.OnMotionNotifyEvent (evnt);
92
93 if (DragActive)
94 SetPositionFromCursor (evnt.X, evnt.Y);
95
96 return false;
97 }
98
99 protected override bool OnScrollEvent (EventScroll evnt)
100 {
101 if (!SliderArea.Contains ((int) evnt.X, (int) evnt.Y))
102 return false;
103
104 switch (evnt.Direction) {
105 case ScrollDirection.Down:
106 case ScrollDirection.Left:
107 (MItem as SliderMenuItem).Position =
108 Math.Max (0.0, Math.Min (1.0, (MItem as SliderMenuItem).Position - StepSize));
109 break;
110 case ScrollDirection.Up:
111 case ScrollDirection.Right:
112 (MItem as SliderMenuItem).Position =
113 Math.Max (0.0, Math.Min (1.0, (MItem as SliderMenuItem).Position + StepSize));
114 break;
115 }
116
117 QueueDraw ();
118 return false;
119 }
120
121 protected override bool OnExposeEvent (EventExpose evnt)
122 {
123 if (!IsRealized)
124 return false;
125
126 Gdk.Rectangle allocation = Allocation;
127
128 using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
129 if (MItem.ShowIcons)
130 DrawMenuItemIcon (cr);
131
132 int offset = Padding;
133 if (MenuShowingIcons)
134 offset += MenuHeight + Padding;
135
136 LinearGradient lg;
137 RadialGradient rg;
138 Cairo.Color white = new Cairo.Color (1, 1, 1, 1);
139 Cairo.Color black = new Cairo.Color (0, 0, 0, 1);
140
141 double x = (int)(allocation.X + offset) + 0.5;
142 double y = (int)(allocation.Y + (MenuHeight - Size / 2.0) / 2.0) + 0.5;
143 double sliderwidth = allocation.Width - offset - Padding;
144 double sliderheight = Size / 2.0;
145 double px = (int)(allocation.X + offset + (allocation.Width - offset - Padding - Size)
146 * (MItem as SliderMenuItem).Position) + 0.5;
147 double py = (int)(allocation.Y + (MenuHeight - Size) / 2.0) + 0.5;
148
149 cr.LineWidth = 1.0;
150
151 // draw marker
152 cr.RoundedRectangle (px, py, Size, Size, Size / 2.0);
153 rg = new RadialGradient (px + 1.66 * Size / 3.0, py + 1.66 * Size / 3.0, 0,
154 px + Size / 2.0, py + Size / 2.0, Size / 2.0);
155 rg.AddColorStop (0, white.SetAlpha (MItem.Disabled ? 0.4 : Selected ? 0.7 : 0.6));
156 rg.AddColorStop (1, black.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.6 : 0.4));
157 cr.Pattern = rg;
158 cr.FillPreserve ();
159 rg.Destroy ();
160 cr.RoundedRectangle (x, y, sliderwidth, sliderheight, 0);
161 cr.FillRule = Cairo.FillRule.EvenOdd;
162 cr.Clip ();
163
164 // background gradient round-box
165 cr.RoundedRectangle (x + 1, y + 1, sliderwidth - 2, sliderheight - 2, 4.5);
166 lg = new LinearGradient (x + 1, y + 1, x + 1, y + Size / 2.0);
167 lg.AddColorStop (0, black.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.3 : 0.25));
168 lg.AddColorStop (1, black.SetAlpha (MItem.Disabled ? 0.05 : Selected ? 0.1 : 0.05));
169 cr.Pattern = lg;
170 cr.FillPreserve ();
171 lg.Destroy ();
172 // dark middle stroke
173 cr.Color = black.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.4 : 0.25);
174 cr.Stroke ();
175
176 // light outer stroke
177 cr.RoundedRectangle (x, y, sliderwidth, sliderheight, 5.0);
178 lg = new LinearGradient (x, y, x, y + Size / 2.0);
179 lg.AddColorStop (0, white.SetAlpha (0));
180 lg.AddColorStop (1, white.SetAlpha (MItem.Disabled ? 0.2 : Selected ? 0.5 : 0.3));
181 cr.Pattern = lg;
182 cr.Stroke ();
183 lg.Destroy ();
184
185 // dark inner stroke
186 cr.RoundedRectangle (x + 2, y + 2, sliderwidth - 4, sliderheight - 4, 4.0);
187 lg = new LinearGradient (x + 2, y + 2, x + 2, y + 2 + Size / 2.0);
188 lg.AddColorStop (0, black.SetAlpha (MItem.Disabled ? 0.05 : Selected ? 0.05 : 0.1));
189 lg.AddColorStop (1, black.SetAlpha (0));
190 cr.Pattern = lg;
191 cr.Stroke ();
192 lg.Destroy ();
193
194 // colored indication of value
195 if (Highlight) {
196 Gdk.Color gdkColor = Style.Backgrounds [(int) StateType.Selected].SetMinimumValue (90);
197 Cairo.Color highlightColor = new Cairo.Color ((double) gdkColor.Red / ushort.MaxValue,
198 (double) gdkColor.Green / ushort.MaxValue,
199 (double) gdkColor.Blue / ushort.MaxValue,
200 1.0);
201 cr.Color = (MItem.Disabled ? highlightColor.SetSaturation (0).SetAlpha (0.4) :
202 highlightColor.SetAlpha (Selected ? 1.0 : 0.95));
203 cr.RoundedRectangle (x + 2, y + 2, px - allocation.X - offset, Size / 2.0 - 4, 3.0);
204 cr.Fill ();
205 }
206
207 //update the areas
208 SliderArea = new Gdk.Rectangle (offset, 0, (int)sliderwidth, MenuHeight);
209 DragArea = new Gdk.Rectangle ((int)px - allocation.X, (int)py - allocation.Y, Size, Size);
210
211 (cr.Target as IDisposable).Dispose ();
212 }
213
214 return true;
215 }
216
217 public override void Dispose ()
218 {
219 (MItem as SliderMenuItem).PositionChanged -= ItemPositionChanged;
220
221 base.Dispose ();
222 }
223 }
224}
0225
=== added file 'Docky/Docky/Menus/TextMenuItemWidget.cs'
--- Docky/Docky/Menus/TextMenuItemWidget.cs 1970-01-01 00:00:00 +0000
+++ Docky/Docky/Menus/TextMenuItemWidget.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,121 @@
1//
2// Copyright (C) 2009 Jason Smith, Robert Dyer
3// Copyright (C) 2010 Robert Dyer
4// Copyright (C) 2011 Rico Tzschichholz
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15//
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Collections.Generic;
22
23using Cairo;
24using Gdk;
25using Gtk;
26
27using Docky.Services;
28using Docky.CairoHelper;
29
30namespace Docky.Menus
31{
32 internal class TextMenuItemWidget : MenuItemWidget
33 {
34 internal TextMenuItemWidget (MenuItem item) : base(item)
35 {
36 CalcTextWidth ();
37 }
38
39 protected void CalcTextWidth ()
40 {
41 using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
42 char accel;
43
44 if (MItem.Mnemonic.HasValue)
45 layout.SetMarkupWithAccel (MItem.Text, '_', out accel);
46 else
47 layout.SetMarkup (MItem.Text);
48 layout.FontDescription = Style.FontDescription;
49 layout.Ellipsize = Pango.EllipsizeMode.End;
50 layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
51 layout.FontDescription.Weight = Pango.Weight.Bold;
52
53 Pango.Rectangle logical, ink;
54 layout.GetPixelExtents (out ink, out logical);
55
56 TextWidth = Math.Min (MaxWidth, Math.Max (MinWidth, logical.Width));
57 HasTooltip = TextWidth < logical.Width;
58
59 layout.Context.Dispose ();
60 }
61
62 SetSize ();
63 }
64
65 protected override void ItemTextChanged (object sender, EventArgs e)
66 {
67 CalcTextWidth ();
68 QueueDraw ();
69 }
70
71 protected override bool OnExposeEvent (EventExpose evnt)
72 {
73 if (!IsRealized)
74 return false;
75
76 Gdk.Rectangle allocation = Allocation;
77
78 using (Cairo.Context cr = Gdk.CairoHelper.Create (evnt.Window)) {
79 if (Selected && !MItem.Disabled) {
80 cr.Rectangle (allocation.X, allocation.Y, allocation.Width, allocation.Height);
81 cr.Color = TextColor.SetAlpha (.1);
82 cr.Fill ();
83 }
84
85 if (MItem.ShowIcons)
86 DrawMenuItemIcon (cr);
87
88 using (Pango.Layout layout = DockServices.Drawing.ThemedPangoLayout ()) {
89 char accel;
90 if (MItem.Mnemonic.HasValue)
91 layout.SetMarkupWithAccel (MItem.Text, '_', out accel);
92 else
93 layout.SetMarkup (MItem.Text);
94 layout.Width = Pango.Units.FromPixels (TextWidth);
95 layout.FontDescription = Style.FontDescription;
96 layout.Ellipsize = Pango.EllipsizeMode.End;
97 layout.FontDescription.AbsoluteSize = Pango.Units.FromPixels (FontSize);
98 layout.FontDescription.Weight = Pango.Weight.Bold;
99
100 Pango.Rectangle logical, ink;
101 layout.GetPixelExtents (out ink, out logical);
102
103 int offset = Padding;
104 if (MenuShowingIcons)
105 offset += MenuHeight + Padding;
106
107 cr.MoveTo (allocation.X + offset, allocation.Y + (allocation.Height - logical.Height) / 2);
108 Pango.CairoHelper.LayoutPath (cr, layout);
109 cr.Color = TextColor.SetAlpha (MItem.Disabled ? 0.5 : 1);
110 cr.Fill ();
111
112 layout.Context.Dispose ();
113 }
114
115 (cr.Target as IDisposable).Dispose ();
116 }
117
118 return true;
119 }
120 }
121}
0122
=== modified file 'Docky/Makefile.am'
--- Docky/Makefile.am 2010-12-01 21:43:00 +0000
+++ Docky/Makefile.am 2012-02-04 07:53:22 +0000
@@ -41,6 +41,8 @@
41 Docky/Menus/DockMenu.cs \41 Docky/Menus/DockMenu.cs \
42 Docky/Menus/MenuItemWidget.cs \42 Docky/Menus/MenuItemWidget.cs \
43 Docky/Menus/SeparatorWidget.cs \43 Docky/Menus/SeparatorWidget.cs \
44 Docky/Menus/SliderMenuItemWidget.cs \
45 Docky/Menus/TextMenuItemWidget.cs \
44 Docky/Menus/DockItemMenu.cs46 Docky/Menus/DockItemMenu.cs
4547
46RESOURCES = \48RESOURCES = \
4749
=== modified file 'Docky/gtk-gui/gui.stetic'
--- Docky/gtk-gui/gui.stetic 2011-12-22 15:47:35 +0000
+++ Docky/gtk-gui/gui.stetic 2012-02-04 07:53:22 +0000
@@ -5,12 +5,9 @@
5 <target-gtk-version>2.12</target-gtk-version>5 <target-gtk-version>2.12</target-gtk-version>
6 </configuration>6 </configuration>
7 <import>7 <import>
8 <widget-library name="notify-sharp, Version=0.4.0.0, Culture=neutral, PublicKeyToken=2df29c54e245917a" />
8 <widget-library name="wnck-sharp, Version=2.20.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />9 <widget-library name="wnck-sharp, Version=2.20.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
9 <widget-library name="gnomedesktop-sharp, Version=2.20.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />10 <widget-library name="gnomedesktop-sharp, Version=2.20.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
10 <widget-library name="../../Docky.Services/bin/Debug/Docky.Services.dll" />
11 <widget-library name="../../Docky.Items/bin/Debug/Docky.Items.dll" />
12 <widget-library name="../../Docky.DBus/bin/Debug/Docky.DBus.dll" />
13 <widget-library name="../../Docky.Widgets/bin/Debug/Docky.Widgets.dll" />
14 <widget-library name="../bin/Debug/Docky.exe" internal="true" />11 <widget-library name="../bin/Debug/Docky.exe" internal="true" />
15 </import>12 </import>
16 <widget class="Gtk.Bin" id="Docky.Interface.DockPreferences" design-size="500 356">13 <widget class="Gtk.Bin" id="Docky.Interface.DockPreferences" design-size="500 356">
1714
=== modified file 'StandardPlugins/Makefile.am'
--- StandardPlugins/Makefile.am 2011-02-23 20:17:57 +0000
+++ StandardPlugins/Makefile.am 2012-02-04 07:53:22 +0000
@@ -12,6 +12,7 @@
12 NPR \12 NPR \
13 RecentDocuments \13 RecentDocuments \
14 SessionManager \14 SessionManager \
15 Sound \
15 Timer \16 Timer \
16 Trash \17 Trash \
17 Weather \18 Weather \
@@ -31,6 +32,7 @@
31 NPR \32 NPR \
32 RecentDocuments \33 RecentDocuments \
33 SessionManager \34 SessionManager \
35 Sound \
34 Timer \36 Timer \
35 Trash \37 Trash \
36 Weather \38 Weather \
3739
=== added directory 'StandardPlugins/Sound'
=== added file 'StandardPlugins/Sound/Makefile.am'
--- StandardPlugins/Sound/Makefile.am 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/Makefile.am 2012-02-04 07:53:22 +0000
@@ -0,0 +1,45 @@
1# Simple component buildsystem
2include $(top_srcdir)/build.rules.docklets.mk
3
4ASSEMBLY = Sound
5
6FILES = \
7 src/SoundDockItem.cs \
8 src/SoundItemProvider.cs \
9 src/PulseAudio-Sharp/Channel.cs \
10 src/PulseAudio-Sharp/Client.cs \
11 src/PulseAudio-Sharp/Context.cs \
12 src/PulseAudio-Sharp/GLib.cs \
13 src/PulseAudio-Sharp/MainLoop.cs \
14 src/PulseAudio-Sharp/Operation.cs \
15 src/PulseAudio-Sharp/PropList.cs \
16 src/PulseAudio-Sharp/Sample.cs \
17 src/PulseAudio-Sharp/ServerInfo.cs \
18 src/PulseAudio-Sharp/Sink.cs \
19 src/PulseAudio-Sharp/SinkInput.cs \
20 src/PulseAudio-Sharp/UnmanagedCallbackManager.cs \
21 src/PulseAudio-Sharp/Util.cs \
22 src/PulseAudio-Sharp/Volume.cs
23
24RESOURCES = \
25 Resources/Sound.addin.xml
26
27PROJECT_REFERENCES= \
28 Docky.CairoHelper \
29 Docky.Items \
30 Docky.Services
31
32REFERENCES = \
33 System \
34 System.Core \
35 Mono.Posix \
36 $(MONO_CAIRO_LIBS) \
37 $(GTK_SHARP_20_LIBS) \
38 $(GLIB_SHARP_20_LIBS) \
39 $(GIO_SHARP_LIBS) \
40 $(WNCK_SHARP_10_LIBS)
41
42EXTRA_DIST += Sound.dll.config
43
44plugin_DATA += Sound.dll.config
45
046
=== added directory 'StandardPlugins/Sound/Resources'
=== added file 'StandardPlugins/Sound/Resources/Sound.addin.xml.in'
--- StandardPlugins/Sound/Resources/Sound.addin.xml.in 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/Resources/Sound.addin.xml.in 2012-02-04 07:53:22 +0000
@@ -0,0 +1,25 @@
1<Addin
2 id="Sound"
3 namespace="Docky"
4 version="1.0"
5 isroot="false"
6 defaultEnabled="false"
7 name="Sound"
8 description="Control your Pulseaudio volumes"
9 author="Rico Tzschichholz"
10 icon="preferences-desktop-sound"
11>
12
13 <Dependencies>
14 <Addin id="Items" version="1.0" />
15 </Dependencies>
16
17 <Runtime>
18 <Import assembly="Sound.dll"/>
19 </Runtime>
20
21 <Extension path="/Docky/ItemProvider">
22 <ItemProvider type="Sound.SoundItemProvider" />
23 </Extension>
24
25</Addin>
026
=== added file 'StandardPlugins/Sound/Sound.csproj'
--- StandardPlugins/Sound/Sound.csproj 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/Sound.csproj 2012-02-04 07:53:22 +0000
@@ -0,0 +1,79 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
3 <PropertyGroup>
4 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5 <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
6 <ProductVersion>9.0.21022</ProductVersion>
7 <SchemaVersion>2.0</SchemaVersion>
8 <ProjectGuid>{16B2B0E3-3437-4F07-958D-E961E132F5F5}</ProjectGuid>
9 <OutputType>Library</OutputType>
10 <RootNamespace>Sound</RootNamespace>
11 <AssemblyName>Sound</AssemblyName>
12 <ReleaseVersion>2.2</ReleaseVersion>
13 </PropertyGroup>
14 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
15 <DebugSymbols>true</DebugSymbols>
16 <DebugType>full</DebugType>
17 <Optimize>false</Optimize>
18 <OutputPath>bin\Debug</OutputPath>
19 <DefineConstants>DEBUG</DefineConstants>
20 <ErrorReport>prompt</ErrorReport>
21 <WarningLevel>4</WarningLevel>
22 </PropertyGroup>
23 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
24 <DebugType>none</DebugType>
25 <Optimize>false</Optimize>
26 <OutputPath>bin\Release</OutputPath>
27 <ErrorReport>prompt</ErrorReport>
28 <WarningLevel>4</WarningLevel>
29 </PropertyGroup>
30 <ItemGroup>
31 <Reference Include="gtk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
32 <Package>gtk-sharp-2.0</Package>
33 </Reference>
34 <Reference Include="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
35 <Package>glib-sharp-2.0</Package>
36 </Reference>
37 <Reference Include="System" />
38 <Reference Include="System.Core" />
39 <Reference Include="Mono.Posix" />
40 <Reference Include="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f">
41 <Package>gtk-sharp-2.0</Package>
42 </Reference>
43 </ItemGroup>
44 <ItemGroup>
45 <Compile Include="src\SoundDockItem.cs" />
46 <Compile Include="src\SoundItemProvider.cs" />
47 <Compile Include="src\PulseAudio-Sharp\Channel.cs" />
48 <Compile Include="src\PulseAudio-Sharp\Context.cs" />
49 <Compile Include="src\PulseAudio-Sharp\GLib.cs" />
50 <Compile Include="src\PulseAudio-Sharp\MainLoop.cs" />
51 <Compile Include="src\PulseAudio-Sharp\Operation.cs" />
52 <Compile Include="src\PulseAudio-Sharp\PropList.cs" />
53 <Compile Include="src\PulseAudio-Sharp\Sample.cs" />
54 <Compile Include="src\PulseAudio-Sharp\ServerInfo.cs" />
55 <Compile Include="src\PulseAudio-Sharp\Sink.cs" />
56 <Compile Include="src\PulseAudio-Sharp\SinkInput.cs" />
57 <Compile Include="src\PulseAudio-Sharp\UnmanagedCallbackManager.cs" />
58 <Compile Include="src\PulseAudio-Sharp\Util.cs" />
59 <Compile Include="src\PulseAudio-Sharp\Volume.cs" />
60 <Compile Include="src\PulseAudio-Sharp\Client.cs" />
61 </ItemGroup>
62 <ItemGroup>
63 <ProjectReference Include="..\..\Docky.Items\Docky.Items.csproj">
64 <Project>{AB6E0EDD-6443-4F99-9EAC-DABC906F080D}</Project>
65 <Name>Docky.Items</Name>
66 </ProjectReference>
67 <ProjectReference Include="..\..\Docky.Services\Docky.Services.csproj">
68 <Project>{8A6E0EDD-6443-4F99-9EAC-D9CC906F080D}</Project>
69 <Name>Docky.Services</Name>
70 </ProjectReference>
71 </ItemGroup>
72 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
73 <ItemGroup>
74 <Folder Include="Resources\" />
75 </ItemGroup>
76 <ItemGroup>
77 <None Include="Resources\Sound.addin.xml" />
78 </ItemGroup>
79</Project>
0\ No newline at end of file80\ No newline at end of file
181
=== added file 'StandardPlugins/Sound/Sound.dll.config'
--- StandardPlugins/Sound/Sound.dll.config 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/Sound.dll.config 2012-02-04 07:53:22 +0000
@@ -0,0 +1,6 @@
1<configuration>
2 <dllmap dll="pulse-mainloop-glib" target="libpulse-mainloop-glib.so.0" />
3 <dllmap dll="pulse" target="libpulse.so.0" />
4</configuration>
5
6
07
=== added directory 'StandardPlugins/Sound/src'
=== added directory 'StandardPlugins/Sound/src/PulseAudio-Sharp'
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Channel.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,133 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Channel.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 public enum ChannelPosition
26 {
27 Invalid = -1,
28 Mono = 0,
29
30 FrontLeft, /* Apple calls this 'Left' */
31 FrontRight, /* Apple calls this 'Right' */
32 FrontCenter, /* Apple calls this 'Center' */
33
34 Left = FrontLeft,
35 Right = FrontRight,
36 Center = FrontCenter,
37
38 RearCentre, /* Microsoft calls this 'Back Center', Apple calls this 'Center Surround' */
39 RearLeft, /* Microsoft calls this 'Back Left', Apple calls this 'Left Surround' */
40 RearRight, /* Microsoft calls this 'Back Right', Apple calls this 'Right Surround' */
41
42 LFE, /* Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */
43 SubWoofer = LFE,
44
45 FrontLeftOfCenter, /* Apple calls this 'Left Center' */
46 FrontRightOfCenter, /* Apple calls this 'Right Center */
47
48 SideLeft, /* Apple calls this 'Left Surround Direct' */
49 SideRight, /* Apple calls this 'Right Surround Direct' */
50
51 PA_CHANNEL_POSITION_AUX0,
52 PA_CHANNEL_POSITION_AUX1,
53 PA_CHANNEL_POSITION_AUX2,
54 PA_CHANNEL_POSITION_AUX3,
55 PA_CHANNEL_POSITION_AUX4,
56 PA_CHANNEL_POSITION_AUX5,
57 PA_CHANNEL_POSITION_AUX6,
58 PA_CHANNEL_POSITION_AUX7,
59 PA_CHANNEL_POSITION_AUX8,
60 PA_CHANNEL_POSITION_AUX9,
61 PA_CHANNEL_POSITION_AUX10,
62 PA_CHANNEL_POSITION_AUX11,
63 PA_CHANNEL_POSITION_AUX12,
64 PA_CHANNEL_POSITION_AUX13,
65 PA_CHANNEL_POSITION_AUX14,
66 PA_CHANNEL_POSITION_AUX15,
67 PA_CHANNEL_POSITION_AUX16,
68 PA_CHANNEL_POSITION_AUX17,
69 PA_CHANNEL_POSITION_AUX18,
70 PA_CHANNEL_POSITION_AUX19,
71 PA_CHANNEL_POSITION_AUX20,
72 PA_CHANNEL_POSITION_AUX21,
73 PA_CHANNEL_POSITION_AUX22,
74 PA_CHANNEL_POSITION_AUX23,
75 PA_CHANNEL_POSITION_AUX24,
76 PA_CHANNEL_POSITION_AUX25,
77 PA_CHANNEL_POSITION_AUX26,
78 PA_CHANNEL_POSITION_AUX27,
79 PA_CHANNEL_POSITION_AUX28,
80 PA_CHANNEL_POSITION_AUX29,
81 PA_CHANNEL_POSITION_AUX30,
82 PA_CHANNEL_POSITION_AUX31,
83
84 TopCenter, /* Apple calls this 'Top Center Surround' */
85
86 TopFrontLeft, /* Apple calls this 'Vertical Height Left' */
87 TopFrontRight, /* Apple calls this 'Vertical Height Right' */
88 TopFrontCenter, /* Apple calls this 'Vertical Height Center' */
89
90 TopRearLeft, /* Microsoft and Apple call this 'Top Back Left' */
91 TopRearRight, /* Microsoft and Apple call this 'Top Back Right' */
92 TopRearCenter, /* Microsoft and Apple call this 'Top Back Center' */
93
94 PA_CHANNEL_POSITION_MAX
95 }
96
97 [StructLayout (LayoutKind.Sequential)]
98 public struct ChannelMap
99 {
100 public byte channels;
101 [MarshalAs (UnmanagedType.ByValArray, SizeConst=Constants.MaxChannels)]
102 public ChannelPosition [] map;
103
104 private void Init ()
105 {
106 map = new ChannelPosition[Constants.MaxChannels];
107 pa_channel_map_init (ref this);
108 }
109
110 public static ChannelMap StereoMapping ()
111 {
112 ChannelMap mapping = new ChannelMap ();
113 mapping.Init ();
114 pa_channel_map_init_stereo (ref mapping);
115 return mapping;
116 }
117
118 public static ChannelMap MonoMapping ()
119 {
120 ChannelMap mapping = new ChannelMap ();
121 mapping.Init ();
122 pa_channel_map_init_mono (ref mapping);
123 return mapping;
124 }
125
126 [DllImport ("pulse")]
127 private static extern void pa_channel_map_init (ref ChannelMap map);
128 [DllImport ("pulse")]
129 private static extern void pa_channel_map_init_stereo (ref ChannelMap map);
130 [DllImport ("pulse")]
131 private static extern void pa_channel_map_init_mono (ref ChannelMap map);
132 }
133}
0134
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Client.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,118 @@
1//
2// Copyright © 2010 Rico Tzschichholz
3//
4// Client.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 [StructLayout (LayoutKind.Sequential)]
26 public class ClientInfo
27 {
28 public UInt32 index; /**< Index of the client */
29 IntPtr name; /**< Name of the client */
30 public UInt32 owner_module; /**< Index of the owning module of this client, or PA_INVALID_INDEX */
31 IntPtr driver; /**< Driver name. */
32 IntPtr proplist; /**< Property list \since 0.9.11 */
33
34 public string Name {
35 get {
36 return Marshal.PtrToStringAnsi (name);
37 }
38 }
39
40 public string Driver {
41 get {
42 return Marshal.PtrToStringAnsi (driver);
43 }
44 }
45
46 public PropList Properties {
47 get {
48 return new PropList (proplist);
49 }
50 }
51
52 public ClientInfo()
53 {
54 }
55 }
56
57 public class Client : IDisposable {
58 private void UpdateFromInfo (ClientInfo i)
59 {
60 info = i;
61 Name = i.Name;
62 Driver = i.Driver;
63 Properties = i.Properties.Copy ();
64 }
65
66 private bool disposed = false;
67 private ClientInfo info;
68 private Context context;
69
70 public Client (Context c, ClientInfo info)
71 {
72 context = c;
73 this.info = info;
74 Name = info.Name;
75 Driver = info.Driver;
76 Properties = info.Properties.Copy ();
77 }
78
79 public void Dispose ()
80 {
81 Dispose (true);
82 GC.SuppressFinalize (this);
83 }
84
85 protected virtual void Dispose (bool explicitlyCalled)
86 {
87 if (!disposed) {
88 if (explicitlyCalled) {
89 }
90 disposed = true;
91 }
92 }
93
94 public string Name {
95 get; private set;
96 }
97
98 public string Driver {
99 get; private set;
100 }
101
102 public PropList Properties {
103 get; private set;
104 }
105
106 public UInt32 Index {
107 get {
108 return info.index;
109 }
110 }
111
112 public UInt32 OwnerModule {
113 get {
114 return info.owner_module;
115 }
116 }
117 }
118}
0119
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Context.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,655 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Context.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22using System.Collections.Generic;
23using System.Threading;
24using System.Reflection;
25
26namespace Pulseaudio
27{
28 public enum EventType {
29 Added,
30 Changed,
31 Removed,
32 Error
33 }
34
35 public class ServerEventArgs : EventArgs
36 {
37 public ServerEventArgs (EventType t, UInt32 index)
38 {
39 this.Type = t;
40 this.index = index;
41 }
42 public EventType Type { get; private set; }
43 public UInt32 index { get; private set; }
44 }
45
46 public class Context : IDisposable
47 {
48 private bool disposed = false;
49 private HandleRef context;
50 private MainLoop loop;
51 private UnmanagedCallbackManager cbManager;
52 private ContextNotifyCB notifyCB;
53 private SubscriptionEventCB eventCB;
54
55 public delegate void ConnectionStateHandler ();
56
57 public event ConnectionStateHandler Ready;
58 public event ConnectionStateHandler Connecting;
59
60 public Context ()
61 {
62 Init (Assembly.GetCallingAssembly ().GetName ().Name);
63 }
64
65 public Context (string clientName)
66 {
67 Init (clientName);
68 }
69
70 public void Dispose ()
71 {
72 Dispose (true);
73 GC.SuppressFinalize (this);
74 }
75
76 protected virtual void Dispose (bool explicitlyCalled)
77 {
78 if (!disposed) {
79 pa_context_set_state_callback (context, null, IntPtr.Zero);
80 pa_context_set_subscribe_callback (context, null, IntPtr.Zero);
81 _sinkEventHandler = null;
82 _sinkInputEventHandler = null;
83 _clientEventHandler = null;
84 pa_context_disconnect (context);
85 pa_context_unref (context);
86 if (explicitlyCalled) {
87 loop.Dispose ();
88 } else {
89 GC.ReRegisterForFinalize (loop);
90 }
91 disposed = true;
92 }
93 }
94
95 ~Context ()
96 {
97 Dispose (false);
98 }
99
100 private void Init (string clientName)
101 {
102 cbManager = new UnmanagedCallbackManager ();
103 loop = new GLibMainLoop ();
104 GC.SuppressFinalize (loop);
105 context = new HandleRef (this, pa_context_new (loop.GetAPI (), clientName));
106 notifyCB = ContextNotifyHandler;
107 pa_context_set_state_callback (context, notifyCB, new IntPtr (0));
108 eventCB = SubscriptionEventHandler;
109 pa_context_set_subscribe_callback (context, eventCB, IntPtr.Zero);
110 }
111
112 public void Connect ()
113 {
114 pa_context_connect (context, null, ContextConnectionFlags.None, new IntPtr (0));
115 }
116
117 public UInt32 ServerAPI {
118 get {
119 return pa_context_get_server_protocol_version (context);
120 }
121 }
122
123 public string Version {
124 get {
125 return "pulseaudio 0.9.16-test2";
126 }
127 }
128
129 public ConnectionState State {
130 get {
131 return pa_context_get_state (context);
132 }
133 }
134
135 [Flags]
136 public enum ContextConnectionFlags
137 {
138 None = 0,
139 NoAutoSpawn = 1,
140 NoFail= 2
141 }
142
143 private delegate void ContextNotifyCB (IntPtr context, IntPtr userdata);
144
145 private void ContextNotifyHandler (IntPtr unused, IntPtr userdata)
146 {
147 switch (pa_context_get_state (context)) {
148 case ConnectionState.Ready:
149 if (Ready != null) {
150 Ready ();
151 }
152 break;
153 case ConnectionState.Connecting:
154 if (Connecting != null) {
155 Connecting ();
156 }
157 break;
158 }
159 }
160
161 public enum ConnectionState {
162 Unconnected, /**< The context hasn't been connected yet */
163 Connecting, /**< A connection is being established */
164 Authorising, /**< The client is authorizing itself to the daemon */
165 SettingName, /**< The client is passing its application name to the daemon */
166 Ready, /**< The connection is established, the context is ready to execute operations */
167 Failed, /**< The connection failed or was disconnected */
168 Terminated /**< The connection was terminated cleanly */
169 }
170
171
172 public Error LastError {
173 get {
174 return new Error (pa_context_errno (context));
175 }
176 }
177
178 [DllImport ("pulse")]
179 private static extern Error.Code pa_context_errno (HandleRef context);
180
181 public Operation EnumerateSinkInputs (SinkInputCallback cb)
182 {
183 int cookie = cbManager.NewCookie ();
184 Action<IntPtr, NativeSinkInputInfo, int, IntPtr> wrapped_cb = (IntPtr c, NativeSinkInputInfo info, int eol, IntPtr userdata) =>
185 {
186 if (eol == 0) {
187 cb (new SinkInput (info, this), eol);
188 } else {
189 cbManager.RemoveDelegate (cookie);
190 }
191 };
192 cbManager.AddDelegate (wrapped_cb, cookie);
193 return new Operation (pa_context_get_sink_input_info_list (context, wrapped_cb, IntPtr.Zero));
194 }
195
196 public delegate void SinkInputCallback (SinkInput info, int eol);
197 private delegate void pa_sink_input_info_cb (IntPtr context, NativeSinkInputInfo info, int eol, IntPtr userdata);
198
199 internal Operation EnumerateSinks (SinkInfoCallback cb)
200 {
201 int cookie = cbManager.NewCookie ();
202 Action<IntPtr, SinkInfo, int, IntPtr> wrapped_cb = (IntPtr c, SinkInfo info, int eol, IntPtr userdata) => {
203 cb (info, eol);
204 if (eol != 0) {
205 cbManager.RemoveDelegate (cookie);
206 }
207 };
208 cbManager.AddDelegate (wrapped_cb, cookie);
209 try {
210 return new Operation (pa_context_get_sink_info_list (context, wrapped_cb, IntPtr.Zero));
211 } catch (ArgumentNullException) {
212 throw new Exception (String.Format ("Error enumerating sinks: {0}", LastError.Message));
213 }
214 }
215
216 internal delegate void SinkInfoCallback (SinkInfo info, int eol);
217 private delegate void pa_sink_info_cb (IntPtr context, SinkInfo info, int eol, IntPtr userdata);
218
219 public Operation EnumerateSinks (SinkCallback cb)
220 {
221 return EnumerateSinks ((SinkInfo info, int eol) => {
222 if (eol == 0) {
223 cb (new Sink (this, info));
224 }
225 });
226 }
227
228 public delegate void SinkCallback (Sink s);
229
230 internal Operation GetSinkInfoByIndex (UInt32 index, SinkInfoCallback cb)
231 {
232 int cookie = cbManager.NewCookie ();
233 Action<IntPtr, SinkInfo, int, IntPtr> wrapped_cb = (IntPtr c, SinkInfo info, int eol, IntPtr userdata) => {
234 cb (info, eol);
235 };
236 cbManager.AddDelegate (wrapped_cb, cookie);
237 try {
238 return new Operation (pa_context_get_sink_info_by_index (context, index, wrapped_cb, IntPtr.Zero));
239 } catch (ArgumentNullException) {
240 throw new Exception (String.Format ("Error getting SinkInfo for index {0}: {1}", index, LastError.Message));
241 }
242 }
243
244 internal Operation GetSinkInfoByIndex (UInt32 index, Action<IntPtr, SinkInfo, int, IntPtr> cb)
245 {
246 try {
247 return new Operation (pa_context_get_sink_info_by_index (context, index, cb, IntPtr.Zero));
248 } catch (ArgumentNullException) {
249 throw new Exception (String.Format ("Error getting SinkInfo for index {0}: {1}", index, LastError.Message));
250 }
251 }
252
253 [DllImport ("pulse")]
254 private static extern IntPtr pa_context_get_sink_info_by_index (HandleRef context,
255 UInt32 index,
256 Action<IntPtr, SinkInfo, int, IntPtr> cb,
257 IntPtr userdata);
258
259 internal Operation GetSinkInputInfoByIndex (UInt32 index, NativeSinkInputInfoCallback cb)
260 {
261 int cookie = cbManager.NewCookie ();
262 Action<IntPtr, NativeSinkInputInfo, int, IntPtr> wrapped_cb = (IntPtr c, NativeSinkInputInfo info, int eol, IntPtr userdata) => {
263 cb (info, eol);
264 };
265 cbManager.AddDelegate (wrapped_cb, cookie);
266 try {
267 return new Operation (pa_context_get_sink_input_info (context, index, wrapped_cb, IntPtr.Zero));
268 } catch (ArgumentNullException) {
269 throw new Exception (String.Format ("Error getting SinkInputInfo for index {0}: {1}", index, LastError.Message));
270 }
271 }
272
273 internal delegate void NativeSinkInputInfoCallback (NativeSinkInputInfo info, int eol);
274 [DllImport ("pulse")]
275 private static extern IntPtr pa_context_get_sink_input_info (HandleRef context,
276 UInt32 index,
277 Action<IntPtr, NativeSinkInputInfo, int, IntPtr> cb,
278 IntPtr userdata);
279
280 public Operation SetSinkVolume (UInt32 index, Volume vol, OperationSuccessCallback cb)
281 {
282 int cookie = cbManager.NewCookie ();
283 Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
284 cb (success);
285 };
286 cbManager.AddDelegate (wrapped_cb, cookie);
287 try {
288 return new Operation (pa_context_set_sink_volume_by_index (context,
289 index,
290 vol,
291 wrapped_cb,
292 IntPtr.Zero));
293 } catch (ArgumentNullException e) {
294 throw new Exception (String.Format ("Error setting sink volume: {0}", LastError.Message));
295 }
296 }
297 [DllImport("pulse")]
298 private static extern IntPtr pa_context_set_sink_volume_by_index(HandleRef context,
299 UInt32 idx,
300 Volume vol,
301 Action<IntPtr, int, IntPtr> cb,
302 IntPtr userdata);
303
304 public Operation SetSinkInputVolume (UInt32 index, Volume vol, OperationSuccessCallback cb)
305 {
306 int cookie = cbManager.NewCookie ();
307 Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
308 cb (success);
309 cbManager.RemoveDelegate (cookie);
310 };
311 cbManager.AddDelegate (wrapped_cb, cookie);
312 try {
313 return new Operation (pa_context_set_sink_input_volume (context,
314 index,
315 vol,
316 wrapped_cb,
317 IntPtr.Zero));
318 } catch (ArgumentNullException) {
319 throw new Exception (String.Format ("Error setting sink input volume: {0}", LastError.Message));
320 }
321 }
322 [DllImport ("pulse")]
323 private static extern IntPtr pa_context_set_sink_input_volume (HandleRef context, UInt32 index, Volume vol, Action<IntPtr, int, IntPtr> cb, IntPtr userdata);
324
325
326 public Operation MoveSinkInputByIndex (UInt32 sinkInputIndex, UInt32 sinkIndex, OperationSuccessCallback cb)
327 {
328 int cookie = cbManager.NewCookie ();
329 Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
330 cb (success);
331 cbManager.RemoveDelegate (cookie);
332 };
333 cbManager.AddDelegate (wrapped_cb, cookie);
334 try {
335 return new Operation (pa_context_move_sink_input_by_index (context,
336 sinkInputIndex,
337 sinkIndex,
338 wrapped_cb,
339 IntPtr.Zero));
340 } catch (ArgumentNullException) {
341 throw new Exception (String.Format ("Error moving SinkInput {0} to sink {1}: {2}", sinkInputIndex, sinkIndex, LastError.Message));
342 }
343 }
344 [DllImport ("pulse")]
345 private static extern IntPtr pa_context_move_sink_input_by_index (HandleRef context, UInt32 sinkInputIndex, UInt32 sinkIndex, Action<IntPtr, int, IntPtr> cb, IntPtr userdata);
346
347
348 public Operation SetSinkMute (UInt32 index, bool mute, OperationSuccessCallback cb)
349 {
350 int cookie = cbManager.NewCookie ();
351 Action<IntPtr, int, IntPtr> wrapped_cb = (IntPtr context, int success, IntPtr userdata) => {
352 cb (success);
353 };
354 cbManager.AddDelegate (wrapped_cb, cookie);
355 try {
356 return new Operation (pa_context_set_sink_mute_by_index (context,
357 index,
358 mute ? 1 : 0,
359 wrapped_cb,
360 IntPtr.Zero));
361 } catch (ArgumentNullException e) {
362 throw new Exception (String.Format ("Error setting sink mute: {0}", LastError.Message));
363 }
364 }
365 [DllImport("pulse")]
366 private static extern IntPtr pa_context_set_sink_mute_by_index (HandleRef context,
367 UInt32 idx,
368 int mute,
369 Action<IntPtr, int, IntPtr> cb,
370 IntPtr userdata);
371
372
373 public Operation EnumerateClients (ClientCallback cb)
374 {
375 return EnumerateClients ((ClientInfo info, int eol) => {
376 if (eol == 0) {
377 cb (new Client (this, info));
378 }
379 });
380 }
381
382 public delegate void ClientCallback (Client c);
383
384 internal Operation EnumerateClients (ClientInfoCallback cb)
385 {
386 int cookie = cbManager.NewCookie ();
387 Action<IntPtr, ClientInfo, int, IntPtr> wrapped_cb = (IntPtr c, ClientInfo info, int eol, IntPtr userdata) => {
388 cb (info, eol);
389 if (eol != 0) {
390 cbManager.RemoveDelegate (cookie);
391 }
392 };
393 cbManager.AddDelegate (wrapped_cb, cookie);
394 try {
395 return new Operation (pa_context_get_client_info_list (context, wrapped_cb, IntPtr.Zero));
396 } catch (ArgumentNullException) {
397 throw new Exception (String.Format ("Error enumerating clients: {0}", LastError.Message));
398 }
399 }
400
401 internal delegate void ClientInfoCallback (ClientInfo info, int eol);
402 private delegate void pa_client_info_cb (IntPtr context, ClientInfo info, int eol, IntPtr userdata);
403
404 internal Operation GetClientInfoByIndex (UInt32 index, ClientInfoCallback cb)
405 {
406 int cookie = cbManager.NewCookie ();
407 Action<IntPtr, ClientInfo, int, IntPtr> wrapped_cb = (IntPtr c, ClientInfo info, int eol, IntPtr userdata) => {
408 cb (info, eol);
409 };
410 cbManager.AddDelegate (wrapped_cb, cookie);
411 try {
412 return new Operation (pa_context_get_client_info (context, index, wrapped_cb, IntPtr.Zero));
413 } catch (ArgumentNullException) {
414 throw new Exception (String.Format ("Error getting ClientInfo for index {0}: {1}", index, LastError.Message));
415 }
416 }
417
418 internal Operation GetClientInfoByIndex (UInt32 index, Action<IntPtr, ClientInfo, int, IntPtr> cb)
419 {
420 try {
421 return new Operation (pa_context_get_client_info (context, index, cb, IntPtr.Zero));
422 } catch (ArgumentNullException) {
423 throw new Exception (String.Format ("Error getting ClientInfo for index {0}: {1}", index, LastError.Message));
424 }
425 }
426 [DllImport("pulse")]
427 private static extern IntPtr pa_context_get_client_info (HandleRef context,
428 UInt32 idx,
429 Action<IntPtr, ClientInfo, int, IntPtr> cb,
430 IntPtr userdata);
431 [DllImport("pulse")]
432 private static extern IntPtr pa_context_get_client_info_list (HandleRef context,
433 Action<IntPtr, ClientInfo, int, IntPtr> cb,
434 IntPtr userdata);
435
436
437 public delegate void OperationSuccessCallback (int success);
438 private delegate void pa_context_success_cb (IntPtr context, int success, IntPtr userdata);
439
440 [DllImport ("pulse")]
441 private static extern IntPtr pa_context_new (IntPtr mainloop_api, string appName);
442 [DllImport ("pulse")]
443 private static extern int pa_context_connect (HandleRef context, string server, ContextConnectionFlags flags,
444 IntPtr spawnApi);
445 [DllImport ("pulse")]
446 private static extern UInt32 pa_context_get_server_protocol_version (HandleRef context);
447 [DllImport ("pulse")]
448 private static extern void pa_context_set_state_callback (HandleRef context,
449 ContextNotifyCB cb,
450 IntPtr userdata);
451 [DllImport ("pulse")]
452 private static extern ConnectionState pa_context_get_state (HandleRef context);
453
454 [DllImport ("pulse")]
455 private static extern IntPtr pa_context_get_sink_input_info_list (HandleRef context,
456 Action<IntPtr, NativeSinkInputInfo, int, IntPtr> cb,
457 IntPtr userdata);
458 [DllImport ("pulse")]
459 private static extern IntPtr pa_context_get_sink_info_list (HandleRef context,
460 Action<IntPtr, SinkInfo, int, IntPtr> cb,
461 IntPtr userdata);
462 [DllImport ("pulse")]
463 private static extern void pa_context_unref (HandleRef context);
464 [DllImport ("pulse")]
465 private static extern void pa_context_disconnect (HandleRef context);
466
467 public delegate void ServerInfoCallback (ServerInfo info);
468 private delegate void pa_server_info_cb (IntPtr context, NativeServerInfo info, IntPtr userdata);
469 [DllImport ("pulse")]
470 private static extern IntPtr pa_context_get_server_info (HandleRef context,
471 Action<IntPtr, NativeServerInfo, IntPtr> cb,
472 IntPtr userdata);
473 public Operation GetServerInfo (ServerInfoCallback cb)
474 {
475 int cookie = cbManager.NewCookie ();
476 Action<IntPtr, NativeServerInfo, IntPtr> wrappedCallback = (_, info, __) => {
477 cb (new ServerInfo (info));
478 cbManager.RemoveDelegate (cookie);
479 };
480 cbManager.AddDelegate (wrappedCallback, cookie);
481 Operation opn;
482 try {
483 opn = new Operation (pa_context_get_server_info (context, wrappedCallback, IntPtr.Zero));
484 } catch (ArgumentNullException) {
485 throw new Exception (String.Format ("Error getting server info: {0}", LastError.Message));
486 }
487 return opn;
488 }
489
490
491 private readonly object eventHandlerLock = new object ();
492 private EventHandler<ServerEventArgs> _sinkEventHandler;
493 public event EventHandler<ServerEventArgs> SinkEvent {
494 add {
495 lock (eventHandlerLock) {
496 if (_sinkEventHandler == null) {
497 UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK);
498 }
499 _sinkEventHandler += value;
500 }
501 }
502 remove {
503 lock (eventHandlerLock) {
504 _sinkEventHandler -= value;
505 if (_sinkEventHandler == null) {
506 UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK);
507 }
508 }
509 }
510 }
511
512 private EventHandler<ServerEventArgs> _sinkInputEventHandler;
513 public event EventHandler<ServerEventArgs> SinkInputEvent {
514 add {
515 lock (eventHandlerLock) {
516 if (_sinkInputEventHandler == null) {
517 UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK_INPUT);
518 }
519 _sinkInputEventHandler += value;
520 }
521 }
522 remove {
523 lock (eventHandlerLock) {
524 _sinkInputEventHandler -= value;
525 if (_sinkInputEventHandler == null) {
526 UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_SINK_INPUT);
527 }
528 }
529 }
530 }
531
532 private EventHandler<ServerEventArgs> _clientEventHandler;
533 public event EventHandler<ServerEventArgs> ClientEvent {
534 add {
535 lock (eventHandlerLock) {
536 if (_clientEventHandler == null) {
537 UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_CLIENT);
538 }
539 _clientEventHandler += value;
540 }
541 }
542 remove {
543 lock (eventHandlerLock) {
544 _clientEventHandler -= value;
545 if (_clientEventHandler == null) {
546 UpdateSubscriptions (SubscriptionMask.PA_SUBSCRIPTION_MASK_CLIENT);
547 }
548 }
549 }
550 }
551
552 private SubscriptionMask registeredEvents = SubscriptionMask.PA_SUBSCRIPTION_MASK_NULL;
553 private void UpdateSubscriptions (SubscriptionMask eventType)
554 {
555 if (registeredEvents != (registeredEvents ^ eventType)) {
556 registeredEvents = registeredEvents ^ eventType;
557 Operation opn = null;
558 try {
559 opn = new Operation (pa_context_subscribe (context,
560 registeredEvents,
561 (_,__,___) => {;},
562 IntPtr.Zero));
563 } catch (ArgumentNullException) {
564 throw new Exception (String.Format ("Error subscribing to server events: {0}", LastError.Message));
565 } finally {
566 if (opn != null) {
567 opn.Dispose ();
568 }
569 }
570 }
571 }
572
573 private void SubscriptionEventHandler (IntPtr context, SubscriptionEventMask e, UInt32 index, IntPtr userdata)
574 {
575 EventType action = EventType.Error;
576 EventHandler<ServerEventArgs> handler = null;
577 switch (e & SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
578 case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_CHANGE:
579 action = EventType.Changed;
580 break;
581 case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_NEW:
582 action = EventType.Added;
583 break;
584 case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_REMOVE:
585 action = EventType.Removed;
586 break;
587 }
588 switch (e & SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
589 case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_SINK:
590 lock (eventHandlerLock) {
591 handler = _sinkEventHandler;
592 }
593 break;
594 case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_SINK_INPUT:
595 lock (eventHandlerLock) {
596 handler = _sinkInputEventHandler;
597 }
598 break;
599 case SubscriptionEventMask.PA_SUBSCRIPTION_EVENT_CLIENT:
600 lock (eventHandlerLock) {
601 handler = _clientEventHandler;
602 }
603 break;
604 }
605 if (handler != null) {
606 handler (this, new ServerEventArgs (action, index));
607 }
608 }
609
610 private delegate void SubscriptionEventCB (IntPtr context, SubscriptionEventMask e, UInt32 index, IntPtr userdata);
611 [DllImport ("pulse")]
612 private static extern IntPtr pa_context_subscribe(HandleRef context,
613 SubscriptionMask m,
614 pa_context_success_cb cb,
615 IntPtr userdata);
616 [DllImport ("pulse")]
617 private static extern void pa_context_set_subscribe_callback (HandleRef context,
618 SubscriptionEventCB cb,
619 IntPtr userData);
620
621 [Flags]
622 private enum SubscriptionMask : uint {
623 PA_SUBSCRIPTION_MASK_NULL = 0x0000U,/**< No events */
624 PA_SUBSCRIPTION_MASK_SINK = 0x0001U,/**< Sink events */
625 PA_SUBSCRIPTION_MASK_SOURCE = 0x0002U,/**< Source events */
626 PA_SUBSCRIPTION_MASK_SINK_INPUT = 0x0004U,/**< Sink input events */
627 PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT = 0x0008U,/**< Source output events */
628 PA_SUBSCRIPTION_MASK_MODULE = 0x0010U,/**< Module events */
629 PA_SUBSCRIPTION_MASK_CLIENT = 0x0020U,/**< Client events */
630 PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 0x0040U,/**< Sample cache events */
631 PA_SUBSCRIPTION_MASK_SERVER = 0x0080U,/**< Other global server changes. */
632 PA_SUBSCRIPTION_MASK_AUTOLOAD = 0x0100U,/**< \deprecated Autoload table events. */
633 PA_SUBSCRIPTION_MASK_CARD = 0x0200U,/**< Card events. \since 0.9.15 */
634 PA_SUBSCRIPTION_MASK_ALL = 0x02ffU/**< Catch all events */
635 }
636 [Flags]
637 private enum SubscriptionEventMask : uint {
638 PA_SUBSCRIPTION_EVENT_SINK = 0x0000U,/**< Event type: Sink */
639 PA_SUBSCRIPTION_EVENT_SOURCE = 0x0001U,/**< Event type: Source */
640 PA_SUBSCRIPTION_EVENT_SINK_INPUT = 0x0002U,/**< Event type: Sink input */
641 PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 0x0003U,/**< Event type: Source output */
642 PA_SUBSCRIPTION_EVENT_MODULE = 0x0004U,/**< Event type: Module */
643 PA_SUBSCRIPTION_EVENT_CLIENT = 0x0005U,/**< Event type: Client */
644 PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 0x0006U,/**< Event type: Sample cache item */
645 PA_SUBSCRIPTION_EVENT_SERVER = 0x0007U,/**< Event type: Global server change, only occurring with PA_SUBSCRIPTION_EVENT_CHANGE. */
646 PA_SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U,/**< \deprecated Event type: Autoload table changes. */
647 PA_SUBSCRIPTION_EVENT_CARD = 0x0009U,/**< Event type: Card \since 0.9.15 */
648 PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 0x000FU,/**< A mask to extract the event type from an event value */
649 PA_SUBSCRIPTION_EVENT_NEW = 0x0000U,/**< A new object was created */
650 PA_SUBSCRIPTION_EVENT_CHANGE = 0x0010U,/**< A property of the object was modified */
651 PA_SUBSCRIPTION_EVENT_REMOVE = 0x0020U,/**< An object was removed */
652 PA_SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U/**< A mask to extract the event operation from an event value */
653 }
654 }
655}
0656
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/GLib.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,65 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// GLib.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Collections.Generic;
22using System.Threading;
23using Pulseaudio;
24using g = GLib;
25
26namespace Pulseaudio.GLib
27{
28 public static class GLibExtensions
29 {
30 public static void Wait (this Operation opn)
31 {
32 //FIXME this is an ugly hack to workaround the problem that MainContext.Iteration ()
33 // seems to iterate not the right mainloop wich results in an endless loop where
34 // operation never leaves the Running state
35 bool timeout = false;
36 g::Timeout.Add (300, delegate {
37 timeout = true;
38 return false;
39 });
40 while (!timeout && opn.State == Operation.Status.Running) {
41 g::MainContext.Iteration ();
42 }
43 }
44
45 public static void ConnectAndWait (this Context context)
46 {
47 ManualResetEvent ready = new ManualResetEvent (false);
48 context.Ready += delegate {
49 ready.Set ();
50 };
51 context.Connect ();
52 //FIXME this is an ugly hack to workaround the problem that MainContext.Iteration ()
53 // seems to iterate not the right mainloop wich results in an endless loop where
54 // operation never leaves the Running state
55 bool timeout = false;
56 g::Timeout.Add (300, delegate {
57 timeout = true;
58 return false;
59 });
60 while (!timeout && !ready.WaitOne (0, true)) {
61 while (g::MainContext.Iteration (false)) {};
62 }
63 }
64 }
65}
066
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/MainLoop.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,76 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// MainLoop.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 public interface MainLoop : IDisposable
26 {
27 //TODO: Make this into an actual managed implementation of a Pulseaudio main loop
28 //For the moment, just require that we can get a pa_mainloop_api pointer out.
29 IntPtr GetAPI ();
30 }
31
32 public class GLibMainLoop : MainLoop
33 {
34 IntPtr pa_mainloop = IntPtr.Zero;
35 bool disposed = false;
36
37 public GLibMainLoop ()
38 {
39 pa_mainloop = pa_glib_mainloop_new (IntPtr.Zero);
40 }
41
42 public IntPtr GetAPI ()
43 {
44 if (disposed) {
45 throw new ObjectDisposedException ("GLibMainLoop", "GLibMainLoop used after being disposed!");
46 }
47 return pa_glib_mainloop_get_api (pa_mainloop);
48 }
49
50 public void Dispose ()
51 {
52 Dispose (true);
53 GC.SuppressFinalize (this);
54 }
55
56 protected virtual void Dispose (bool explicitlyCalled)
57 {
58 if (!disposed) {
59 pa_glib_mainloop_free (pa_mainloop);
60 disposed = true;
61 }
62 }
63
64 ~GLibMainLoop ()
65 {
66 Dispose (false);
67 }
68
69 [DllImport ("pulse-mainloop-glib")]
70 private static extern IntPtr pa_glib_mainloop_new (IntPtr main_context);
71 [DllImport ("pulse-mainloop-glib")]
72 private static extern IntPtr pa_glib_mainloop_get_api (IntPtr pa_mainloop);
73 [DllImport ("pulse-mainloop-glib")]
74 private static extern void pa_glib_mainloop_free (IntPtr pa_mainloop);
75 }
76}
077
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Operation.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,73 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Operation.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 public sealed class Operation : IDisposable
26 {
27 public enum Status
28 {
29 Running,
30 Done,
31 Cancelled
32 }
33
34 private HandleRef handle;
35 internal Operation(IntPtr handle)
36 {
37 if (handle == IntPtr.Zero) {
38 throw new ArgumentNullException ("handle", "Tried to create an Operation with null pa_operation pointer");
39 }
40 this.handle = new HandleRef (this, handle);
41 }
42
43 ~Operation ()
44 {
45 if (handle.Handle != IntPtr.Zero) {
46 pa_operation_unref (handle);
47 }
48 }
49
50 public void Dispose ()
51 {
52 pa_operation_unref (handle);
53 GC.SuppressFinalize (this);
54
55 handle = new HandleRef (this, IntPtr.Zero);
56 }
57
58 public Status State {
59 get {
60 return pa_operation_get_state (handle);
61 }
62 }
63
64 [DllImport ("pulse")]
65 private static extern void pa_operation_unref (HandleRef opn);
66 [DllImport ("pulse")]
67 private static extern Status pa_operation_get_state (HandleRef opn);
68 [DllImport ("pulse")]
69 private static extern void pa_operation_ref (HandleRef opn);
70 [DllImport ("pulse")]
71 private static extern void pa_operation_cancel (HandleRef opn);
72 }
73}
074
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/PropList.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,509 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// PropList.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Collections.Generic;
22using System.Runtime.InteropServices;
23
24namespace Pulseaudio
25{
26 public enum Properties {
27 /// <summary>
28 /// For streams: localized media name, formatted as UTF-8. e.g. "Guns'N'Roses: Civil War".
29 /// </summary>
30 MediaName,
31 /// <summary>
32 /// For streams: localized media title if applicable, formatted as UTF-8. e.g. "Civil War"
33 /// </summary>
34 MediaTitle,
35 /// <summary>
36 /// For streams: localized media artist if applicable, formatted as UTF-8. e.g. "Guns'N'Roses"
37 /// </summary>
38 MediaArtist,
39 /// <summary>
40 /// For streams: localized media copyright string if applicable, formatted as UTF-8. e.g. "Evil Record Corp."
41 /// </summary>
42 MediaCopyright,
43 /// <summary>
44 /// For streams: localized media generator software string if applicable, formatted as UTF-8. e.g. "Foocrop AudioFrobnicator"
45 /// </summary>
46 MediaSoftware,
47 /// <summary>
48 /// For streams: media language if applicable, in standard POSIX format. e.g. "de_DE"
49 /// </summary>
50 MediaLanguage,
51 /// <summary>
52 /// For streams: source filename if applicable, in URI format or local path. e.g. "/home/lennart/music/foobar.ogg"
53 /// </summary>
54 MediaFilename,
55 /// <summary>
56 /// For streams: icon for the media. A binary blob containing PNG image data
57 /// </summary>
58 MediaIcon,
59 /// <summary>
60 /// For streams: an XDG icon name for the media. e.g. "audio-x-mp3"
61 /// </summary>
62 MediaIconName,
63 /// <summary>
64 /// For streams: logic role of this media. One of the strings "video", "music", "game", "event", "phone", "animation", "production", "a11y"
65 /// </summary>
66 MediaRole,
67 /// <summary>
68 /// For event sound streams: XDG event sound name. e.g. "message-new-email" (Event sound streams are those with media.role set to "event")
69 /// </summary>
70 EventID,
71 /// <summary>
72 /// 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."
73 /// </summary>
74 EventDescription,
75 /// <summary>
76 /// 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"
77 /// </summary>
78 EventMouseX,
79 /// <summary>
80 /// 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"
81 /// </summary>
82 EventMouseY,
83 /// <summary>
84 /// 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"
85 /// </summary>
86 EventMouseHPos,
87 /// <summary>
88 /// 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"
89 /// </summary>
90 EventMouseVPos,
91 /// <summary>
92 /// 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"
93 /// </summary>
94 EventMouseButton,
95 /// <summary>
96 /// For streams that belong to a window on the screen: localized window title. e.g. "Totem Music Player"
97 /// </summary>
98 WindowName,
99 /// <summary>
100 /// For streams that belong to a window on the screen: a textual id for identifying a window logically. e.g. "org.gnome.Totem.MainWindow"
101 /// </summary>
102 WindowID,
103 /// <summary>
104 /// For streams that belong to a window on the screen: window icon. A binary blob containing PNG image data
105 /// </summary>
106 WindowIcon,
107 /// <summary>
108 /// For streams that belong to a window on the screen: an XDG icon name for the window. e.g. "totem"
109 /// </summary>
110 WindowIconName,
111 /// <summary>
112 /// 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
113 /// </summary>
114 WindowX,
115 /// <summary>
116 /// 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
117 /// </summary>
118 WindowY,
119 /// <summary>
120 /// 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
121 /// </summary>
122 WindowWidth,
123 /// <summary>
124 /// 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
125 /// </summary>
126 WindowHeight,
127 /// <summary>
128 /// 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
129 /// </summary>
130 WindowHPos,
131 /// <summary>
132 /// 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
133 /// </summary>
134 WindowVPos,
135 /// <summary>
136 /// 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
137 /// </summary>
138 WindowDesktop,
139 /// <summary>
140 /// For streams that belong to an X11 window on the screen: the X11 display string. e.g. ":0.0"
141 /// </summary>
142 WindowX11Display,
143 /// <summary>
144 /// 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"
145 /// </summary>
146 WindowX11Screen,
147 /// <summary>
148 /// 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"
149 /// </summary>
150 WindowX11Monitor,
151 /// <summary>
152 /// For streams that belong to an X11 window on the screen: the window XID, an integer formatted as string. e.g. "25632"
153 /// </summary>
154 WindowX11XID,
155 /// <summary>
156 /// For clients/streams: localized human readable application name. e.g. "Totem Music Player"
157 /// </summary>
158 ApplicationName,
159 /// <summary>
160 /// For clients/streams: a textual id for identifying an application logically. e.g. "org.gnome.Totem"
161 /// </summary>
162 ApplicationID,
163 /// <summary>
164 /// For clients/streams: a version string e.g. "0.6.88"
165 /// </summary>
166 ApplicationVersion,
167 /// <summary>
168 /// For clients/streams: an XDG icon name for the application. e.g. "totem"
169 /// </summary>
170 ApplicationIconName,
171 /// <summary>
172 /// For clients/streams: application language if applicable, in standard POSIX format. e.g. "de_DE"
173 /// </summary>
174 ApplicationLanguage,
175 /// <summary>
176 /// For clients/streams on UNIX: application process PID, an integer formatted as string. e.g. "4711"
177 /// </summary>
178 ApplicationPID,
179 /// <summary>
180 /// For clients/streams: application process name. e.g. "totem"
181 /// </summary>
182 ApplicationProcessBinary,
183 /// <summary>
184 /// For clients/streams: application user name. e.g. "lennart"
185 /// </summary>
186 ApplicationProcessUser,
187 /// <summary>
188 /// For clients/streams: host name the application runs on. e.g. "omega"
189 /// </summary>
190 ApplicationProcessHost,
191 /// <summary>
192 /// For clients/streams: the D-Bus host id the application runs on. e.g. "543679e7b01393ed3e3e650047d78f6e"
193 /// </summary>
194 ApplicationProcessMachineID,
195 /// <summary>
196 /// 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"
197 /// </summary>
198 ApplicationProcessSessionID,
199 /// <summary>
200 /// For devices: device string in the underlying audio layer's format. e.g. "surround51:0"
201 /// </summary>
202 DeviceString,
203 /// <summary>
204 /// For devices: API this device is access with. e.g. "alsa"
205 /// </summary>
206 DeviceAPI,
207 /// <summary>
208 /// For devices: localized human readable device one-line description, e.g. "Foobar Industries USB Headset 2000+ Ultra"
209 /// </summary>
210 DeviceDescription,
211 /// <summary>
212 /// For devices: bus path to the device in the OS' format. e.g. "/sys/bus/pci/devices/0000:00:1f.2"
213 /// </summary>
214 DeviceBusPath,
215 /// <summary>
216 /// For devices: serial number if applicable. e.g. "4711-0815-1234"
217 /// </summary>
218 DeviceSerial,
219 /// <summary>
220 /// For devices: vendor ID if applicable. e.g. 1274
221 /// </summary>
222 DeviceVendorID,
223 /// <summary>
224 /// For devices: vendor name if applicable. e.g. "Foocorp Heavy Industries"
225 /// </summary>
226 DeviceVendorName,
227 /// <summary>
228 /// For devices: product ID if applicable. e.g. 4565
229 /// </summary>
230 DeviceProductID,
231 /// <summary>
232 /// For devices: product name if applicable. e.g. "SuperSpeakers 2000 Pro"
233 /// </summary>
234 DeviceProductName,
235 /// <summary>
236 /// For devices: device class. One of "sound", "modem", "monitor", "filter"
237 /// </summary>
238 DeviceClass,
239 /// <summary>
240 /// For devices: form factor if applicable. One of "internal", "speaker", "handset", "tv", "webcam", "microphone", "headset", "headphone", "hands-free", "car", "hifi", "computer", "portable"
241 /// </summary>
242 DeviceFormFactor,
243 /// <summary>
244 /// For devices: bus of the device if applicable. One of "isa", "pci", "usb", "firewire", "bluetooth"
245 /// </summary>
246 DeviceBus,
247 /// <summary>
248 /// For devices: an XDG icon name for the device. e.g. "sound-card-speakers-usb"
249 /// </summary>
250 DeviceIconName,
251 /// <summary>
252 /// For devices: access mode of the device if applicable. One of "mmap", "mmap_rewrite", "serial"
253 /// </summary>
254 DeviceAccessMode,
255 /// <summary>
256 /// For filter devices: master device id if applicable.
257 /// </summary>
258 DeviceMasterDevice,
259 /// <summary>
260 /// For devices: buffer size in bytes, integer formatted as string.
261 /// </summary>
262 DeviceBufferSize,
263 /// <summary>
264 /// For devices: fragment size in bytes, integer formatted as string.
265 /// </summary>
266 DeviceFragmentSize,
267 /// <summary>
268 /// For devices: profile identifier for the profile this devices is in. e.g. "analog-stereo", "analog-surround-40", "iec958-stereo", ...
269 /// </summary>
270 DeviceProfileName,
271 /// <summary>
272 /// For devices: human readable one-line description of the profile this device is in. e.g. "Analog Stereo", ...
273 /// </summary>
274 DeviceProfileDescription,
275 /// <summary>
276 /// 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
277 /// </summary>
278 DeviceIntendedRoles,
279 /// <summary>
280 /// For modules: the author's name, formatted as UTF-8 string. e.g. "Lennart Poettering"
281 /// </summary>
282 ModuleAuthor,
283 /// <summary>
284 /// 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"
285 /// </summary>
286 ModuleDescription,
287 /// <summary>
288 /// For modules: a human readable usage description of the module's arguments formatted as UTF-8
289 /// </summary>
290 ModuleUsage,
291 /// <summary>
292 /// For modules: a version string for the module. e.g. "0.9.15"
293 /// </summary>
294 ModuleVersion
295 }
296
297 public class PropList : IDisposable
298 {
299 private static readonly Dictionary<Properties, string> propertyTable = new Dictionary<Properties, string> {
300 {Properties.MediaName, "media.name"},
301 {Properties.MediaTitle, "media.title"},
302 {Properties.MediaArtist, "media.artist"},
303 {Properties.MediaCopyright, "media.copyright"},
304 {Properties.MediaSoftware, "media.software"},
305 {Properties.MediaLanguage, "media.language"},
306 {Properties.MediaFilename, "media.filename"},
307 {Properties.MediaIconName, "media.icon_name"},
308 {Properties.MediaRole, "media.role"},
309 {Properties.EventID, "event.id"},
310 {Properties.EventDescription, "event.description"},
311 {Properties.EventMouseX, "event.mouse.x"},
312 {Properties.EventMouseY, "event.mouse.y"},
313 {Properties.EventMouseHPos, "event.mouse.hpos"},
314 {Properties.EventMouseVPos, "event.mouse.vpos"},
315 {Properties.EventMouseButton, "event.mouse.button"},
316 {Properties.WindowName, "window.name"},
317 {Properties.WindowID, "window.id"},
318 {Properties.WindowIconName, "window.icon_name"},
319 {Properties.WindowX, "window.x"},
320 {Properties.WindowY, "window.y"},
321 {Properties.WindowWidth, "window.width"},
322 {Properties.WindowHeight, "window.height"},
323 {Properties.WindowHPos, "window.hpos"},
324 {Properties.WindowVPos, "window.vpos"},
325 {Properties.WindowDesktop, "window.desktop"},
326 {Properties.WindowX11Display, "window.x11.display"},
327 {Properties.WindowX11Screen, "window.x11.screen"},
328 {Properties.WindowX11Monitor, "window.x11.monitor"},
329 {Properties.WindowX11XID, "window.x11.xid"},
330 {Properties.ApplicationName, "application.name"},
331 {Properties.ApplicationID, "application.id"},
332 {Properties.ApplicationVersion, "application.version"},
333 {Properties.ApplicationIconName, "application.icon_name"},
334 {Properties.ApplicationLanguage, "application.language"},
335 {Properties.ApplicationPID, "application.process.id"},
336 {Properties.ApplicationProcessBinary, "application.process.binary"},
337 {Properties.ApplicationProcessUser, "application.process.user"},
338 {Properties.ApplicationProcessHost, "application.process.host"},
339 {Properties.ApplicationProcessMachineID, "application.process.machine_id"},
340 {Properties.ApplicationProcessSessionID, "application.process.session_id"},
341 {Properties.DeviceString, "device.string"},
342 {Properties.DeviceAPI, "device.api"},
343 {Properties.DeviceDescription, "device.description"},
344 {Properties.DeviceBusPath, "device.bus_path"},
345 {Properties.DeviceSerial, "device.serial"},
346 {Properties.DeviceVendorID, "device.vendor.id"},
347 {Properties.DeviceVendorName, "device.vendor.name"},
348 {Properties.DeviceProductID, "device.product.id"},
349 {Properties.DeviceProductName, "device.product.name"},
350 {Properties.DeviceClass, "device.class"},
351 {Properties.DeviceFormFactor, "device.form_factor"},
352 {Properties.DeviceBus, "device.bus"},
353 {Properties.DeviceIconName, "device.icon_name"},
354 {Properties.DeviceAccessMode, "device.access_mode"},
355 {Properties.DeviceMasterDevice, "device.master_device"},
356 {Properties.DeviceBufferSize, "device.buffering.buffer_size"},
357 {Properties.DeviceFragmentSize, "device.buffering.fragment_size"},
358 {Properties.DeviceProfileName, "device.profile.name"},
359 {Properties.DeviceProfileDescription, "device.profile.description"},
360 {Properties.DeviceIntendedRoles, "device.intended_roles"},
361 {Properties.ModuleAuthor, "module.author"},
362 {Properties.ModuleDescription, "module.description"},
363 {Properties.ModuleUsage, "module.version"},
364 {Properties.ModuleVersion, "module.version"}
365 };
366 private HandleRef handle;
367 private bool needDispose;
368
369 public PropList ()
370 {
371 IntPtr listPtr = pa_proplist_new ();
372 if (listPtr == IntPtr.Zero) {
373 throw new NullReferenceException ();
374 }
375 handle = new HandleRef (this, listPtr);
376 needDispose = true;
377 }
378
379 /// <summary>
380 /// Internal-only PropList constructor taking an unmanaged pointer.
381 /// </summary>
382 /// <param name="listPtr">
383 /// A <see cref="IntPtr"/>. Unmanaged pointer to a pa_proplist.
384 /// </param>
385 /// <remarks>
386 /// It is assumed that native code owns the pa_proplist pointer. As such, it will not be free'd on Dispose ().
387 /// If the managed code should own this pointer, set needDispose to true after calling this constructor.
388 /// </remarks>
389 internal PropList (IntPtr listPtr)
390 {
391 if (listPtr == IntPtr.Zero) {
392 throw new NullReferenceException ();
393 }
394 handle = new HandleRef (this, listPtr);
395 needDispose = false;
396 GC.SuppressFinalize (this);
397 }
398
399 ~PropList ()
400 {
401 if (needDispose) {
402 pa_proplist_free (handle);
403 }
404 }
405
406 public void Dispose ()
407 {
408 if (needDispose) {
409 pa_proplist_free (handle);
410 }
411 handle = new HandleRef (this, IntPtr.Zero);
412 needDispose = false;
413
414 GC.SuppressFinalize (this);
415 }
416
417 public byte[] this[string key]
418 {
419 get {
420 IntPtr data = new IntPtr ();
421 IntPtr size = new IntPtr ();
422 pa_proplist_get (handle, key, ref data, ref size);
423
424 byte[] retVal = new byte[size.ToInt32 ()];
425 Marshal.Copy (data, retVal, 0, size.ToInt32 ());
426
427 return retVal;
428 }
429 set {
430 if (value == null) {
431 throw new ArgumentNullException ("value", "Setting a PropList key to 'null' is unsupported");
432 }
433 pa_proplist_set (handle, key, value, new IntPtr (Marshal.SizeOf (value[0]) * value.Length));
434 }
435 }
436
437 public string this[Properties prop]
438 {
439 get {
440 return Marshal.PtrToStringAnsi (pa_proplist_gets (handle, propertyTable[prop]));
441 }
442 set {
443 pa_proplist_sets (handle, propertyTable[prop], value);
444 }
445 }
446
447 public bool Empty {
448 get {
449 // TODO: Work out why pa_proplist_isempty doesn't return 0 when the list is empty.
450 return (Count == 0);
451 }
452 }
453
454 public uint Count {
455 get {
456 return pa_proplist_size (handle);
457 }
458 }
459
460 public void Clear ()
461 {
462 pa_proplist_clear (handle);
463 }
464
465 public PropList Copy ()
466 {
467 PropList clone = new PropList (pa_proplist_copy (handle));
468 clone.needDispose = true;
469 GC.ReRegisterForFinalize (clone);
470 return clone;
471 }
472
473 public IEnumerable<string> Keys {
474 get {
475 IntPtr cookie = new IntPtr (0);
476 IntPtr key;
477 key = pa_proplist_iterate (handle, ref cookie);
478 while (key != IntPtr.Zero) {
479 yield return Marshal.PtrToStringAnsi (key);
480 key = pa_proplist_iterate (handle, ref cookie);
481 }
482 yield break;
483 }
484 }
485
486 [DllImport ("pulse")]
487 private static extern int pa_proplist_isempty (HandleRef list);
488 [DllImport ("pulse")]
489 private static extern IntPtr pa_proplist_new ();
490 [DllImport ("pulse")]
491 private static extern void pa_proplist_clear (HandleRef list);
492 [DllImport ("pulse")]
493 private static extern uint pa_proplist_size (HandleRef list);
494 [DllImport ("pulse")]
495 private static extern int pa_proplist_sets (HandleRef list, string key, string value);
496 [DllImport ("pulse")]
497 private static extern int pa_proplist_set (HandleRef list, string key, byte[] data, IntPtr size);
498 [DllImport ("pulse")]
499 private static extern IntPtr pa_proplist_gets (HandleRef list, string key);
500 [DllImport ("pulse")]
501 private static extern int pa_proplist_get (HandleRef list, string key, ref IntPtr data, ref IntPtr size);
502 [DllImport ("pulse")]
503 private static extern IntPtr pa_proplist_iterate (HandleRef list, ref IntPtr cookie);
504 [DllImport ("pulse")]
505 private static extern IntPtr pa_proplist_copy (HandleRef list);
506 [DllImport ("pulse")]
507 private static extern void pa_proplist_free (HandleRef list);
508 }
509}
0510
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Sample.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,89 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Sample.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 public enum SampleFormat
26 {
27 U8,
28 /**< Unsigned 8 Bit PCM */
29
30 ALAW,
31 /**< 8 Bit a-Law */
32
33 ULAW,
34 /**< 8 Bit mu-Law */
35
36 S16LE,
37 /**< Signed 16 Bit PCM, little endian (PC) */
38
39 S16BE,
40 /**< Signed 16 Bit PCM, big endian */
41
42 Float32LE,
43 /**< 32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0 */
44
45 Float32BE,
46 /**< 32 Bit IEEE floating point, big endian, range -1.0 to 1.0 */
47
48 S32LE,
49 /**< Signed 32 Bit PCM, little endian (PC) */
50
51 S32BE,
52 /**< Signed 32 Bit PCM, big endian */
53
54 S24LE,
55 /**< Signed 24 Bit PCM packed, little endian (PC). \since 0.9.15 */
56
57 S24BE,
58 /**< Signed 24 Bit PCM packed, big endian. \since 0.9.15 */
59
60 S24in32LE,
61 /**< Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC). \since 0.9.15 */
62
63 S24in32BE,
64 /**< Signed 24 Bit PCM in LSB of 32 Bit words, big endian. \since 0.9.15 */
65
66 Max,
67 /**< Upper limit of valid sample types */
68
69 Invalid = -1
70 /**< An invalid value */
71 }
72
73 [StructLayout (LayoutKind.Sequential)]
74 public struct SampleSpec
75 {
76 public SampleFormat format;
77 public UInt32 rate;
78 public Byte channels;
79
80 public UInt64 BytesPerSecond {
81 get {
82 return (UInt64) pa_bytes_per_second (ref this);
83 }
84 }
85
86 [DllImport ("pulse")]
87 private static extern UIntPtr pa_bytes_per_second (ref SampleSpec spec);
88 }
89}
090
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/ServerInfo.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,120 @@
1//
2// ServerInfo.cs
3//
4// Author:
5// Christopher James Halse Rogers <raof@ubuntu.com>
6//
7// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
8//
9// This program is free software: you can redistribute it and/or modify
10// it under the terms of the GNU Lesser General Public License as published by
11// the Free Software Foundation, either version 3 of the License, or
12// (at your option) any later version.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17// GNU Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public License
20// along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22using System;
23using System.Runtime.InteropServices;
24
25namespace Pulseaudio
26{
27 [StructLayout(LayoutKind.Sequential)]
28 internal class NativeServerInfo
29 {
30 public IntPtr user_name;
31 public IntPtr host_name;
32 public IntPtr server_version;
33 public IntPtr server_name;
34 public SampleSpec sample_spec;
35 public IntPtr default_sink_name;
36 public IntPtr default_source_name;
37 public UInt32 cookie;
38 public ChannelMap channel_map;
39 }
40
41 public class ServerInfo
42 {
43 internal ServerInfo (NativeServerInfo info)
44 {
45 UserName = Marshal.PtrToStringAnsi (info.user_name);
46 HostName = Marshal.PtrToStringAnsi (info.host_name);
47 Version = Marshal.PtrToStringAnsi (info.server_version);
48 Name = Marshal.PtrToStringAnsi (info.server_name);
49 DefaultSampleSpec = info.sample_spec;
50 DefaultSinkName = Marshal.PtrToStringAnsi (info.default_sink_name);
51 DefaultSourceName = Marshal.PtrToStringAnsi (info.default_source_name);
52 Cookie = info.cookie;
53 DefaultChannelMap = info.channel_map;
54 }
55
56 /// <summary>
57 /// Name of the user running the daemon process
58 /// </summary>
59 public string UserName {
60 get;
61 private set;
62 }
63 /// <summary>
64 /// Host name of the machine the daemon is running on
65 /// </summary>
66 public string HostName {
67 get;
68 private set;
69 }
70 /// <summary>
71 /// Version string of the daemon
72 /// </summary>
73 public string Version {
74 get;
75 private set;
76 }
77 /// <summary>
78 /// Package name of the daemon (usually "pulseaudio")
79 /// </summary>
80 public string Name {
81 get;
82 private set;
83 }
84 /// <summary>
85 /// Default sample specification for this daemon
86 /// </summary>
87 public SampleSpec DefaultSampleSpec {
88 get;
89 private set;
90 }
91 /// <summary>
92 /// Name of the default sink for this daemon
93 /// </summary>
94 public string DefaultSinkName {
95 get;
96 private set;
97 }
98 /// <summary>
99 /// Name of the default source for this daemon
100 /// </summary>
101 public string DefaultSourceName {
102 get;
103 private set;
104 }
105 /// <summary>
106 /// A random cookie identifying this daemon process
107 /// </summary>
108 public UInt32 Cookie {
109 get;
110 private set;
111 }
112 /// <summary>
113 /// Default channel map for this server
114 /// </summary>
115 public ChannelMap DefaultChannelMap {
116 get;
117 private set;
118 }
119 }
120}
0121
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Sink.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,346 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Sink.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Collections.Generic;
22using System.Linq;
23using System.Runtime.InteropServices;
24
25namespace Pulseaudio
26{
27 public enum SinkState {
28 Invalid = -1,
29 /**< This state is used when the server does not support sink state introspection \since 0.9.15 */
30
31 Running = 0,
32 /**< Running, sink is playing and used by at least one non-corked sink-input \since 0.9.15 */
33
34 Idle = 1,
35 /**< When idle, the sink is playing but there is no non-corked sink-input attached to it \since 0.9.15 */
36
37 Suspended = 2,
38 /**< When suspended, actual sink access can be closed, for instance \since 0.9.15 */
39 }
40
41 [Flags]
42 public enum SinkFlags {
43 HardwareVolume = 0x0001,
44 /**< Supports hardware volume control */
45
46 LatencyQuery = 0x0002,
47 /**< Supports latency querying */
48
49 IsHardware = 0x0004,
50 /**< Is a hardware sink of some kind, in contrast to
51 * "virtual"/software sinks \since 0.9.3 */
52
53 IsNetwork = 0x0008,
54 /**< Is a networked sink of some kind. \since 0.9.7 */
55
56 HardwareMute = 0x0010,
57 /**< Supports hardware mute control \since 0.9.11 */
58
59 dBVolume = 0x0020,
60 /**< Volume can be translated to dB with pa_sw_volume_to_dB()
61 * \since 0.9.11 */
62
63 FlatVolume = 0x0040,
64 /**< This sink is in flat volume mode, i.e. always the maximum of
65 * the volume of all connected inputs. \since 0.9.15 */
66
67 DynamicLatency = 0x0080
68 /**< The latency can be adjusted dynamically depending on the
69 * needs of the connected streams. \since 0.9.15 */
70 }
71
72 [StructLayout (LayoutKind.Sequential)]
73 public class SinkInfo
74 {
75 IntPtr name; /**< Name of the sink */
76 public UInt32 index; /**< Index of the sink */
77 IntPtr description; /**< Description of this sink */
78 public SampleSpec sample_spec; /**< Sample spec of this sink */
79 public ChannelMap channel_map; /**< Channel map */
80 public UInt32 owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX */
81 public Volume volume; /**< Volume of the sink */
82 public int mute; /**< Mute switch of the sink */
83 public UInt32 monitor_source; /**< Index of the monitor source connected to this sink */
84 IntPtr monitor_source_name; /**< The name of the monitor source */
85 public UInt64 latency; /**< Length of queued audio in the output buffer. */
86 IntPtr driver; /**< Driver name. */
87 public SinkFlags flags; /**< Flags */
88 IntPtr proplist; /**< Property list \since 0.9.11 */
89 public UInt64 configured_latency; /**< The latency this device has been configured to. \since 0.9.11 */
90 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 */
91 public SinkState state; /**< State \since 0.9.15 */
92 public UInt32 n_volume_steps; /**< Number of volume steps for sinks which do not support arbitrary volumes. \since 0.9.15 */
93 public UInt32 card; /**< Card index, or PA_INVALID_INDEX. \since 0.9.15 */
94
95 public string Name {
96 get {
97 return Marshal.PtrToStringAnsi (name);
98 }
99 }
100
101 public string Description {
102 get { return Marshal.PtrToStringAnsi (description);
103 }
104 }
105
106 public string MonitorSourceName {
107 get {
108 return Marshal.PtrToStringAnsi (monitor_source_name);
109 }
110 }
111
112 public string Driver {
113 get {
114 return Marshal.PtrToStringAnsi (driver);
115 }
116 }
117
118 public PropList Properties {
119 get {
120 return new PropList (proplist);
121 }
122 }
123
124 public SinkInfo()
125 {
126 }
127 }
128
129 public class Sink : IDisposable {
130 private void UpdateFromInfo (SinkInfo i)
131 {
132 if (info.volume != i.volume || info.mute != i.mute) {
133 info = i;
134 Description = i.Description;
135 MonitorSourceName = i.MonitorSourceName;
136 Driver = i.Driver;
137 EventHandler<VolumeChangedEventArgs> handler;
138 lock (eventHandlerLock) {
139 handler = _volumeChangedHandler;
140 }
141 if (handler != null) {
142 handler (this, new VolumeChangedEventArgs (i.volume));
143 }
144 } else {
145 info = i;
146 Description = i.Description;
147 MonitorSourceName = i.MonitorSourceName;
148 Driver = i.Driver;
149 }
150 }
151
152 private bool disposed = false;
153 private SinkInfo info;
154 private Context context;
155
156 public Sink (Context c, SinkInfo info)
157 {
158 context = c;
159 this.info = info;
160 Name = info.Name;
161 Description = info.Description;
162 MonitorSourceName = info.MonitorSourceName;
163 Driver = info.Driver;
164 Properties = info.Properties.Copy ();
165 context.SinkEvent += HandleRawSinkEvent;
166 }
167
168 public void Dispose ()
169 {
170 Dispose (true);
171 GC.SuppressFinalize (this);
172 }
173
174 protected virtual void Dispose (bool explicitlyCalled)
175 {
176 if (!disposed) {
177 if (explicitlyCalled) {
178 //Unregister our server info callbacks
179 context.SinkEvent -= HandleRawSinkEvent;
180 }
181 disposed = true;
182 }
183 }
184
185 public string Name {
186 get; private set;
187 }
188
189 public string Description {
190 get; private set;
191 }
192
193 public PropList Properties {
194 get; private set;
195 }
196
197 public UInt32 Index {
198 get {
199 return info.index;
200 }
201 }
202
203 public SampleSpec SampleSpec {
204 get {
205 //SampleSpec is a struct, so this passes a copy.
206 return info.sample_spec;
207 }
208 }
209
210 public ChannelMap ChannelMap {
211 get {
212 //ChannelMap is a struct, so this returns a copy.
213 return info.channel_map;
214 }
215 }
216 public UInt32 OwnerModule {
217 get {
218 return info.owner_module;
219 }
220 }
221
222 public Volume Volume {
223 get {
224 return info.volume.Copy ();
225 }
226 }
227
228 public bool Mute {
229 get {
230 return (info.mute > 0);
231 }
232 }
233
234 public UInt32 MonitorSource {
235 get {
236 return info.monitor_source;
237 }
238 }
239
240 public string MonitorSourceName {
241 get; private set;
242 }
243
244 public UInt64 Latency {
245 get {
246 return info.latency;
247 }
248 }
249
250 public string Driver {
251 get; private set;
252 }
253
254 public SinkFlags Flags {
255 get {
256 return info.flags;
257 }
258 }
259
260 public UInt64 ConfiguredLatency {
261 get {
262 return info.configured_latency;
263 }
264 }
265
266 public UInt32 BaseVolume {
267 get {
268 return info.base_volume;
269 }
270 }
271
272 public SinkState State {
273 get {
274 return info.state;
275 }
276 }
277
278 public UInt32 NumVolumeSteps {
279 get {
280 return info.n_volume_steps;
281 }
282 }
283
284 public UInt32 Card {
285 get {
286 return info.card;
287 }
288 }
289
290 public delegate void VolumeCallback (Volume vol);
291 public Operation GetVolume (VolumeCallback cb)
292 {
293 return context.GetSinkInfoByIndex (info.index, (SinkInfo i, int eol) =>
294 {
295 if (eol == 0) {
296 cb (i.volume);
297 }} );
298 }
299
300 public Operation SetVolume (Volume vol, Context.OperationSuccessCallback cb)
301 {
302 return context.SetSinkVolume (info.index, vol, cb);
303 }
304
305
306 private readonly object eventHandlerLock = new object ();
307 private EventHandler<VolumeChangedEventArgs> _volumeChangedHandler;
308 public event EventHandler<VolumeChangedEventArgs> VolumeChanged {
309 add {
310 lock (eventHandlerLock) {
311 _volumeChangedHandler += value;
312 }
313 }
314 remove {
315 lock (eventHandlerLock) {
316 _volumeChangedHandler -= value;
317 }
318 }
319 }
320
321 void HandleRawSinkEvent (object sender, ServerEventArgs e)
322 {
323 Context c = sender as Context;
324 if (e.Type == EventType.Changed) {
325 if (e.index == info.index) {
326 Operation o = c.GetSinkInfoByIndex (info.index, (SinkInfo i, int eol) =>
327 {
328 if (eol == 0) {
329 UpdateFromInfo (i);
330 }});
331 o.Dispose ();
332 }
333 }
334 }
335
336 public class VolumeChangedEventArgs : EventArgs
337 {
338 public VolumeChangedEventArgs (Volume vol)
339 {
340 newVolume = vol;
341 }
342
343 public Volume newVolume { get; private set; }
344 }
345 }
346}
0347
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/SinkInput.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,179 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// SinkInput.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 [StructLayout (LayoutKind.Sequential)]
26 internal class NativeSinkInputInfo
27 {
28 public UInt32 index; /**< Index of the sink input */
29 public IntPtr name; /**< Name of the sink input */
30 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 */
31 public UInt32 client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */
32 public UInt32 sink; /**< Index of the connected sink */
33 public SampleSpec sample_spec; /**< The sample specification of the sink input */
34 public ChannelMap channel_map; /**< Channel map */
35 public Volume volume; /**< The volume of this sink input */
36 public UInt64 buffer_usec; /**< Latency due to buffering in sink input, see pa_latency_info for details */
37 public UInt64 sink_usec; /**< Latency of the sink device, see pa_latency_info for details */
38 public IntPtr resample_method; /**< The resampling method used by this sink input. */
39 public IntPtr driver; /**< Driver name */
40 public int mute; /**< Stream muted \since 0.9.7 */
41 public IntPtr prop_handle; /**< Property list \since 0.9.11 */
42 }
43
44 public class SinkInput : IDisposable
45 {
46 private bool disposed = false;
47 private Context ctx;
48 private NativeSinkInputInfo info;
49
50 internal SinkInput (NativeSinkInputInfo info, Context c)
51 {
52 ctx = c;
53 this.info = info;
54 Name = Marshal.PtrToStringAnsi (info.name);
55 ResampleMethod = Marshal.PtrToStringAnsi (info.resample_method);
56 Driver = Marshal.PtrToStringAnsi (info.driver);
57 PropList temp = new PropList (info.prop_handle);
58 Properties = temp.Copy ();
59 ctx.SinkInputEvent += HandleSinkInputEvent;
60 }
61
62 public void Dispose ()
63 {
64 Dispose (true);
65 GC.SuppressFinalize (this);
66 }
67
68 public void Dispose (bool explicitlyCalled)
69 {
70 if (!disposed) {
71 if (explicitlyCalled) {
72 Properties.Dispose ();
73 ctx.SinkInputEvent -= HandleSinkInputEvent;
74 }
75 disposed = true;
76 }
77 }
78
79 public string Name {
80 get;
81 private set;
82 }
83
84 public string ResampleMethod {
85 get;
86 private set;
87 }
88
89 public string Driver {
90 get;
91 private set;
92 }
93
94 public UInt32 Index {
95 get { return info.index; }
96 }
97 public UInt32 OwnerModule {
98 get { return info.owner_module; }
99 }
100 public UInt32 Client {
101 get { return info.client; }
102 }
103 public UInt32 Sink {
104 get { return info.sink; }
105 }
106 public SampleSpec Spec {
107 get { return info.sample_spec; }
108 }
109 public ChannelMap Map {
110 get { return info.channel_map; }
111 }
112 public Volume Volume {
113 get { return info.volume.Copy (); }
114 }
115 public UInt64 BufferLatency {
116 get { return info.buffer_usec; }
117 }
118 public UInt64 SinkLatency {
119 get { return info.sink_usec; }
120 }
121 public bool Mute {
122 get { return (info.mute > 0); }
123 }
124 public PropList Properties {
125 get;
126 private set;
127 }
128
129 public Operation SetVolume (Volume v, Context.OperationSuccessCallback cb)
130 {
131 return ctx.SetSinkInputVolume (Index, v, cb);
132 }
133
134 public class VolumeChangedEventArgs : EventArgs
135 {
136 public VolumeChangedEventArgs (Volume vol)
137 {
138 newVolume = vol;
139 }
140
141 public Volume newVolume { get; private set; }
142 }
143
144 public event EventHandler<VolumeChangedEventArgs> VolumeChanged;
145
146 private void HandleSinkInputEvent (object sender, ServerEventArgs args)
147 {
148 Context c = sender as Context;
149 if (args.Type == EventType.Changed && args.index == Index && c != null) {
150 Operation o = ctx.GetSinkInputInfoByIndex (Index, (NativeSinkInputInfo i, int eol) => {
151 if (eol == 0) {
152 UpdateInfo (i);
153 }
154 });
155 o.Dispose ();
156 }
157 }
158
159 private void UpdateInfo (NativeSinkInputInfo info)
160 {
161 if (this.info.volume != info.volume || this.info.mute != info.mute) {
162 this.info = info;
163 ResampleMethod = Marshal.PtrToStringAnsi (info.resample_method);
164 PropList temp = new PropList (info.prop_handle);
165 Properties = temp.Copy ();
166 EventHandler<VolumeChangedEventArgs> handler;
167 handler = VolumeChanged;
168 if (handler != null) {
169 handler (this, new VolumeChangedEventArgs (info.volume));
170 }
171 } else {
172 this.info = info;
173 ResampleMethod = Marshal.PtrToStringAnsi (info.resample_method);
174 PropList temp = new PropList (info.prop_handle);
175 Properties = temp.Copy ();
176 }
177 }
178 }
179}
0180
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/UnmanagedCallbackManager.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,84 @@
1//
2// UnmanagedCallbackManager.cs
3//
4// Author:
5// Christopher James Halse Rogers <raof@ubuntu.com>
6//
7// Copyright © 2010 Christopher James Halse Rogers <raof@ubuntu.com>
8//
9// This program is free software: you can redistribute it and/or modify
10// it under the terms of the GNU Lesser General Public License as published by
11// the Free Software Foundation, either version 3 of the License, or
12// (at your option) any later version.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17// GNU Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public License
20// along with this program. If not, see <http://www.gnu.org/licenses/>.
21
22using System;
23using System.Linq;
24using System.Collections.Generic;
25
26namespace Pulseaudio
27{
28 internal class UnmanagedCallbackManager
29 {
30 private Dictionary<int, Action> delegates;
31 private List<int> pendingCookies;
32
33 public UnmanagedCallbackManager ()
34 {
35 delegates = new Dictionary<int, Action> ();
36 pendingCookies = new List<int> ();
37 }
38
39 public void AddDelegate (Action act, int cookie)
40 {
41 if (pendingCookies.Contains (cookie)) {
42 delegates[cookie] = act;
43 pendingCookies.Remove (cookie);
44 } else {
45 throw new Exception ();
46 }
47 }
48
49 public void AddDelegate<T1, T2, T3, T4> (Action<T1, T2, T3, T4> act, int cookie)
50 {
51 AddDelegate (() => act(default(T1), default(T2), default(T3), default(T4)), cookie);
52 }
53
54 public void AddDelegate<T1, T2, T3> (Action<T1, T2, T3> act, int cookie)
55 {
56 AddDelegate (() => act(default (T1), default (T2), default (T3)), cookie);
57 }
58
59 public void RemoveDelegate (int cookie)
60 {
61 if (!delegates.ContainsKey (cookie)) {
62 throw new Exception ();
63 }
64 delegates.Remove (cookie);
65 }
66
67 public int PendingCallbackCount {
68 get {
69 return delegates.Count ();
70 }
71 }
72
73 public int NewCookie ()
74 {
75 int nextCookie, maxDelegateCookie, maxPendingCookie;
76 maxDelegateCookie = delegates.Count () == 0 ? 0 : delegates.Keys.Max ();
77 maxPendingCookie = pendingCookies.Count () == 0 ? 0 : pendingCookies.Max ();
78
79 nextCookie = Math.Max (maxDelegateCookie, maxPendingCookie) + 1;
80 pendingCookies.Add (nextCookie);
81 return nextCookie;
82 }
83 }
84}
085
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Util.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,184 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Util.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Runtime.InteropServices;
22
23namespace Pulseaudio
24{
25 public enum ErrorCode {
26 OK = 0, /**< No error */
27 AccessFailure, /**< Access failure */
28 UnknownCommand, /**< Unknown command */
29 InvalidArgument, /**< Invalid argument */
30 EntityExists, /**< Entity exists */
31 NoSuchEntity, /**< No such entity */
32 ConnectionRefused, /**< Connection refused */
33 Protocol, /**< Protocol error */
34 Timeout, /**< Timeout */
35 NoAuthKey, /**< No authorization key */
36 Internal, /**< Internal error */
37 ConnectionTerminated, /**< Connection terminated */
38 Killed, /**< Entity killed */
39 InvalidServer, /**< Invalid server */
40 ModuleInitFailed, /**< Module initialization failed */
41 BadState, /**< Bad state */
42 NoData, /**< No data */
43 Version, /**< Incompatible protocol version */
44 TooLarge, /**< Data too large */
45 NotSupported, /**< Operation not supported \since 0.9.5 */
46 Unknown, /**< The error code was unknown to the client */
47 NoExtension, /**< Extension does not exist. \since 0.9.12 */
48 Obsolete, /**< Obsolete functionality. \since 0.9.15 */
49 NotImplemented, /**< Missing implementation. \since 0.9.15 */
50 Forked, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */
51 IO, /**< An IO error happened. \since 0.9.16 */
52 PA_ERR_MAX /**< Not really an error but the first invalid error code */
53 }
54
55 public class Error
56 {
57 public enum Code {
58 OK = 0, /**< No error */
59 AccessFailure, /**< Access failure */
60 UnknownCommand, /**< Unknown command */
61 InvalidArgument, /**< Invalid argument */
62 EntityExists, /**< Entity exists */
63 NoSuchEntity, /**< No such entity */
64 ConnectionRefused, /**< Connection refused */
65 Protocol, /**< Protocol error */
66 Timeout, /**< Timeout */
67 NoAuthKey, /**< No authorization key */
68 Internal, /**< Internal error */
69 ConnectionTerminated, /**< Connection terminated */
70 Killed, /**< Entity killed */
71 InvalidServer, /**< Invalid server */
72 ModuleInitFailed, /**< Module initialization failed */
73 BadState, /**< Bad state */
74 NoData, /**< No data */
75 Version, /**< Incompatible protocol version */
76 TooLarge, /**< Data too large */
77 NotSupported, /**< Operation not supported \since 0.9.5 */
78 Unknown, /**< The error code was unknown to the client */
79 NoExtension, /**< Extension does not exist. \since 0.9.12 */
80 Obsolete, /**< Obsolete functionality. \since 0.9.15 */
81 NotImplemented, /**< Missing implementation. \since 0.9.15 */
82 Forked, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */
83 IO, /**< An IO error happened. \since 0.9.16 */
84 PA_ERR_MAX /**< Not really an error but the first invalid error code */
85 }
86
87 private Code error;
88 private string error_string;
89
90 internal Error (Code error)
91 {
92 this.error = error;
93 }
94
95 public Code ErrorCode {
96 get {
97 return error;
98 }
99 }
100
101 public string Message {
102 get {
103 if (string.IsNullOrEmpty (error_string)) {
104 error_string = Marshal.PtrToStringAnsi (pa_strerror (Convert.ToInt32 (error)));
105 }
106 return error_string;
107 }
108 }
109
110 public override string ToString ()
111 {
112 return string.Format("[Error: {0}]", Message);
113 }
114
115 public override bool Equals (object obj)
116 {
117 Error other = obj as Error;
118 if (other != null) {
119 return (error == other.error);
120 }
121 if (obj is Code) {
122 return error == (Code)obj;
123 }
124 return base.Equals (obj);
125 }
126
127 public bool Equals (Error b)
128 {
129 return error == b.error;
130 }
131
132 public override int GetHashCode ()
133 {
134 return error.GetHashCode ();
135 }
136
137 public static bool operator==(Error a, Code b)
138 {
139 return a.error == b;
140 }
141
142 public static bool operator!=(Error a, Code b)
143 {
144 return !(a==b);
145 }
146
147 public static bool operator==(Code a, Error b)
148 {
149 return b == a;
150 }
151
152 public static bool operator!=(Code a, Error b)
153 {
154 return !(a==b);
155 }
156
157 public static bool operator==(Error a, Error b)
158 {
159 if (Object.ReferenceEquals (a, b)) {
160 return true;
161 }
162 return a.error == b.error;
163 }
164
165 public static bool operator!=(Error a, Error b)
166 {
167 return !(a==b);
168 }
169
170 [DllImport ("pulse")]
171 private static extern IntPtr pa_strerror (int error);
172 }
173
174 public static class Util
175 {
176 public static string ErrorStringFromErrno (ErrorCode error)
177 {
178 return Marshal.PtrToStringAnsi (pa_strerror (Convert.ToInt32 (error)));
179 }
180
181 [DllImport ("pulse")]
182 private static extern IntPtr pa_strerror (int error);
183 }
184}
0185
=== added file 'StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs'
--- StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/PulseAudio-Sharp/Volume.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,137 @@
1//
2// Copyright © 2009 Christopher James Halse Rogers <raof@ubuntu.com>
3//
4// Volume.cs is a part of Pulseaudio#
5//
6// Pulseaudio# is free software: you can redistribute it and/or modify
7// it under the terms of the GNU Lesser General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10//
11// Pulseaudio# is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU Lesser General Public License for more details.
15//
16// You should have received a copy of the GNU Lesser General Public License
17// along with Pulseaudio#. If not, see <http://www.gnu.org/licenses/>.
18//
19
20using System;
21using System.Text;
22using System.Runtime.InteropServices;
23
24namespace Pulseaudio
25{
26 public class Constants
27 {
28 public const int MaxChannels = 32;
29 }
30
31 [StructLayout (LayoutKind.Sequential)]
32 public class Volume
33 {
34 private Byte channels;
35 [MarshalAs (UnmanagedType.ByValArray, SizeConst=Constants.MaxChannels)]
36 private UInt32 [] values;
37
38 private static readonly UInt32 norm = 0x10000U;
39 private static readonly UInt32 mute = 0;
40
41 public Volume()
42 {
43 values = new uint[Constants.MaxChannels];
44 pa_cvolume_init (this);
45 channels = 2;
46 }
47
48 /// <summary>
49 /// Reset the volume of all channels to no change (100%)
50 /// </summary>
51 public void Reset ()
52 {
53 pa_cvolume_set (this, channels, norm);
54 }
55
56 /// <summary>
57 /// Set the volume of all channels.
58 /// </summary>
59 /// <param name="val">
60 /// A <see cref="System.Double"/>. The nominal range is 0 - 1.0, with 0 corresponding to mute
61 /// and 1.0 corresponding to full volume (or, equivalently, no volume adjustment).
62 /// It is possible to set values > 1.0. This is not an error, but is not generally a good idea.
63 /// </param>
64 public void Set (double val)
65 {
66 pa_cvolume_set (this, channels, (UInt32)(val * norm));
67 }
68
69 public Volume Copy ()
70 {
71 Volume retVal = new Volume ();
72 retVal.channels = channels;
73 values.CopyTo (retVal.values, 0);
74 return retVal;
75 }
76
77 public override bool Equals (object obj)
78 {
79 if (obj is Volume) {
80 return (this as Volume) == (obj as Volume);
81 }
82 return false;
83 }
84
85 public override int GetHashCode ()
86 {
87 return base.GetHashCode ();
88 }
89
90 public static bool operator== (Volume a, Volume b)
91 {
92 return pa_cvolume_equal (a, b) != 0;
93 }
94 public static bool operator!= (Volume a, Volume b)
95 {
96 return !(a==b);
97 }
98
99 public override string ToString ()
100 {
101 StringBuilder buffer = new StringBuilder (PA_CVOLUME_SNPRINT_BUFFER_MAX);
102 pa_cvolume_snprint (buffer, new IntPtr (buffer.Capacity), this);
103 return buffer.ToString ();
104 }
105
106 public double ToScalarAvg ()
107 {
108 UInt32 rawVol = pa_cvolume_avg (this);
109 return ((((double)rawVol) - mute) / norm);
110 }
111
112 public void Modify (double amount)
113 {
114 if (amount > 0) {
115 pa_cvolume_inc (this, (UInt32)((amount - mute) * norm));
116 } else {
117 pa_cvolume_dec (this, (UInt32)((-amount - mute) * norm));
118 }
119 }
120
121 [DllImport ("pulse")]
122 private static extern int pa_cvolume_equal (Volume a, Volume b);
123 [DllImport ("pulse")]
124 private static extern void pa_cvolume_init ([Out] Volume a);
125 [DllImport ("pulse")]
126 private static extern void pa_cvolume_set ([In, Out] Volume vol, uint channels, UInt32 val);
127 [DllImport ("pulse")]
128 private static extern void pa_cvolume_snprint (System.Text.StringBuilder buffer, IntPtr bufferSize, Volume vol);
129 static readonly int PA_CVOLUME_SNPRINT_BUFFER_MAX = 320;
130 [DllImport ("pulse")]
131 private static extern UInt32 pa_cvolume_avg (Volume vol);
132 [DllImport ("pulse")]
133 private static extern void pa_cvolume_inc ([In, Out]Volume vol, UInt32 amount);
134 [DllImport ("pulse")]
135 private static extern void pa_cvolume_dec ([In, Out]Volume vol, UInt32 amount);
136 }
137}
0138
=== added file 'StandardPlugins/Sound/src/SoundDockItem.cs'
--- StandardPlugins/Sound/src/SoundDockItem.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/SoundDockItem.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,537 @@
1//
2// Copyright (C) 2011 Rico Tzschichholz
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17
18using System;
19using System.Collections.Generic;
20using System.Collections.ObjectModel;
21using System.Linq;
22using System.Text;
23using System.Threading;
24
25using Mono.Unix;
26
27using Cairo;
28using GLib;
29using Gdk;
30using Wnck;
31
32using Docky.Items;
33using Docky.Menus;
34using Docky.Services;
35using Docky.Services.Prefs;
36
37using Pulseaudio;
38using Pulseaudio.GLib;
39
40namespace Sound
41{
42 public class SoundDockItem : IconDockItem
43 {
44 static IPreferences prefs = DockServices.Preferences.Get <SoundDockItem> ();
45
46 object update_lock = new object ();
47
48 const string AlsaCommandVolumeDown = "amixer set Master 3%- /dev/null";
49 const string AlsaCommandVolumeUp = "amixer set Master 3%+ /dev/null";
50
51 Pulseaudio.Context pa_context;
52
53 List<SinkInput> PASinkInputs = new List<SinkInput> ();
54 public List<SinkInput> UsableSinkInputs {
55 get {
56 if (CurrentSink == null)
57 return new List<SinkInput> ();
58 return PASinkInputs.FindAll (si => si.Sink == CurrentSink.Index && UsableClients.Exists (c => c.Index == si.Client));
59 }
60 }
61
62 List<Client> PAClients = new List<Client> ();
63 public List<Client> UsableClients {
64 get {
65 if (CurrentSink == null)
66 return new List<Client> ();
67 return PAClients.FindAll (c => PASinkInputs.Exists (si => si.Sink == CurrentSink.Index && si.Client == c.Index));
68 }
69 }
70
71 List<Sink> PASinks = new List<Sink> ();
72 Sink current_sink;
73 public Sink CurrentSink {
74 get {
75 //TODO determine current user selected default sink
76 if (PASinks.Any () && (current_sink == null || !PASinks.Contains (current_sink))) {
77 int pref_index = prefs.Get<int> ("CurrentSinkIndex", 0);
78 current_sink = PASinks.Find (s => s.Index == pref_index);
79
80 if (current_sink == null)
81 current_sink = PASinks.DefaultIfEmpty (null).FirstOrDefault ();
82
83 if (current_sink != null) {
84 current_sink.VolumeChanged += HandlePulseaudioSinkVolumeChanged;
85 prefs.Set<int> ("CurrentSinkIndex", (int)current_sink.Index);
86 OnVolumeChanged ();
87 }
88 }
89 return current_sink;
90 }
91 private set {
92 if (current_sink != null)
93 current_sink.VolumeChanged -= HandlePulseaudioSinkVolumeChanged;
94
95 current_sink = value;
96
97 if (current_sink != null) {
98 current_sink.VolumeChanged += HandlePulseaudioSinkVolumeChanged;
99 prefs.Set<int> ("CurrentSinkIndex", (int)current_sink.Index);
100 }
101 OnVolumeChanged ();
102 }
103 }
104
105 bool pa_try_reconnect = false;
106
107 bool IsPulseAudioAvailable {
108 get {
109 if (pa_try_reconnect && pa_context.State == Pulseaudio.Context.ConnectionState.Failed) {
110 lock (update_lock) {
111 Log<SoundDockItem>.Debug ("Reconnecting to Pulseaudio");
112 CurrentSink = null;
113 pa_context.Ready -= HandlePulseaudioContextReady;
114 pa_context.Dispose ();
115 pa_context = new Pulseaudio.Context ("Docky's Sound Docklet");
116 pa_context.Ready += HandlePulseaudioContextReady;
117 pa_context.Connect ();
118 }
119 }
120 return pa_context != null && pa_context.State == Pulseaudio.Context.ConnectionState.Ready && CurrentSink != null;
121 }
122 }
123
124 public override string UniqueID ()
125 {
126 return "SoundControl";
127 }
128
129 public SoundDockItem ()
130 {
131 HoverText = Catalog.GetString ("Volume Control");
132 Icon = "preferences-desktop-sound";
133
134 Log<SoundDockItem>.Debug ("Connecting to Pulseaudio");
135 pa_context = new Pulseaudio.Context ("Docky's Sound Docklet");
136 pa_context.Ready += HandlePulseaudioContextReady;
137 pa_context.Connect ();
138 }
139
140 void HandlePulseaudioContextReady ()
141 {
142 Log<SoundDockItem>.Debug ("Using Pulseaudio (API {0})", pa_context.ServerAPI);
143
144 InitializeSinkList ();
145 InitializeClientList ();
146 InitializeSinkInputList ();
147
148 pa_context.SinkEvent += HandlePulseaudioContextSinkEvent;
149 pa_context.SinkInputEvent += HandlePulseaudioContextSinkInputEvent;
150 pa_context.ClientEvent += HandlePulseaudioContextClientEvent;
151
152 // if we managed to connect once, we will retry if the connection gets lost
153 pa_try_reconnect = true;
154 }
155
156 void InitializeClientList ()
157 {
158 lock (PAClients) {
159 PAClients.ForEach (c => c.Dispose ());
160 PAClients.Clear ();
161 using (Operation o = pa_context.EnumerateClients ((Client client) => {
162 Log<SoundDockItem>.Debug ("Found Client \"{1}\" ({0})", client.Index, client.Name);
163 PAClients.Add (client);
164 }))
165 {
166 o.Wait ();
167 }
168 }
169 }
170
171 void HandlePulseaudioContextClientEvent (object sender, ServerEventArgs e)
172 {
173 lock (PAClients) {
174 if (e.Type == Pulseaudio.EventType.Removed) {
175 Client c = PAClients.Find (cl => cl.Index == e.index);
176 if (c != null) {
177 Log<SoundDockItem>.Debug ("Remove Client \"{1}\" ({0})", c.Index, c.Name);
178 PAClients.Remove (c);
179 c.Dispose ();
180 }
181 } else if (e.Type == Pulseaudio.EventType.Added) {
182 //TODO use index directly to get this particular Client
183 using (Operation o = pa_context.EnumerateClients ((Client client) => {
184 if (e.index == client.Index) {
185 Log<SoundDockItem>.Debug ("Add Client \"{1}\" ({0})", client.Index, client.Name);
186 PAClients.Add (client);
187 }
188 }))
189 {
190 o.Wait ();
191 }
192 }
193 }
194 }
195
196 void InitializeSinkInputList ()
197 {
198 lock (PASinkInputs) {
199 PASinkInputs.ForEach (si => si.Dispose ());
200 PASinkInputs.Clear ();
201 using (Operation o = pa_context.EnumerateSinkInputs ((SinkInput sinkinput, int eol) => {
202 if (eol == 0) {
203 Log<SoundDockItem>.Debug ("Found SinkInput {0} for Client {1} \"{2}\"", sinkinput.Index, sinkinput.Client, sinkinput.Name);
204 PASinkInputs.Add (sinkinput);
205 }
206 }))
207 {
208 o.Wait ();
209 }
210 }
211 }
212
213 void HandlePulseaudioContextSinkInputEvent (object sender, ServerEventArgs e)
214 {
215 lock (PASinkInputs) {
216 if (e.Type == Pulseaudio.EventType.Removed) {
217 SinkInput si = PASinkInputs.Find (sii => sii.Index == e.index);
218 if (si != null) {
219 Log<SoundDockItem>.Debug ("Remove SinkInput {0} for Client {1} \"{2}\"", si.Index, si.Client, si.Name);
220 PASinkInputs.Remove (si);
221 si.Dispose ();
222 }
223 } else if (e.Type == Pulseaudio.EventType.Added) {
224 //TODO use index directly to get this particular SinkInput
225 using (Operation o = pa_context.EnumerateSinkInputs ((SinkInput sinkinput, int eol) => {
226 if (eol == 0 && e.index == sinkinput.Index) {
227 Log<SoundDockItem>.Debug ("Add SinkInput {0} for Client {1} \"{2}\"", sinkinput.Index, sinkinput.Client, sinkinput.Name);
228 PASinkInputs.Add (sinkinput);
229 }
230 }))
231 {
232 o.Wait ();
233 }
234 }
235 }
236 }
237
238 void InitializeSinkList ()
239 {
240 lock (PASinks) {
241 PASinks.ForEach (s => {
242 s.VolumeChanged -= HandlePulseaudioSinkVolumeChanged;
243 s.Dispose ();
244 });
245 PASinks.Clear ();
246 using (Operation o = pa_context.EnumerateSinks ((Sink sink) => {
247 Log<SoundDockItem>.Debug ("Found Device \"{1}\" ({0})", sink.Index, sink.Description);
248 PASinks.Add (sink);
249 //OnVolumeChanged ();
250 }))
251 {
252 o.Wait ();
253 }
254 }
255 }
256
257 void HandlePulseaudioContextSinkEvent (object sender, ServerEventArgs e)
258 {
259 lock (PASinks) {
260 if (e.Type == Pulseaudio.EventType.Removed) {
261 Sink s = PASinks.Find (si => si.Index == e.index);
262 if (s != null) {
263 Log<SoundDockItem>.Debug ("Remove Device \"{1}\" ({0})", s.Index, s.Description);
264 PASinks.Remove (s);
265 s.Dispose ();
266 }
267 } else if (e.Type == Pulseaudio.EventType.Added) {
268 //TODO use index to get this particular Sink
269 using (Operation o = pa_context.EnumerateSinks ((Sink sink) => {
270 if (e.index == sink.Index) {
271 Log<SoundDockItem>.Debug ("Add Device \"{1}\" ({0})", sink.Index, sink.Description);
272 PASinks.Add (sink);
273 }
274 }))
275 {
276 o.Wait ();
277 }
278 }
279 }
280 }
281
282 void HandlePulseaudioSinkVolumeChanged (object sender, Sink.VolumeChangedEventArgs e)
283 {
284 OnVolumeChanged ();
285 }
286
287 void OnVolumeChanged ()
288 {
289 if (!IsPulseAudioAvailable) {
290 HoverText = Catalog.GetString ("Volume Control");
291 Icon = "preferences-desktop-sound";
292 return;
293 }
294
295 double volume = CurrentSink.Volume.ToScalarAvg ();
296 Icon = GetVolumeIcon (volume, CurrentSink.Mute);
297 HoverText = String.Format (Catalog.GetString ("Volume {0}%"), Math.Round (volume * 100))
298 + (CurrentSink.Mute ? " (" + Catalog.GetString ("Muted") + ")" : "");
299 }
300
301 void PulseaudioChangeVolume (double val)
302 {
303 if (!IsPulseAudioAvailable)
304 return;
305
306 Pulseaudio.Volume vol = CurrentSink.Volume.Copy ();
307 vol.Modify (val);
308
309 //TODO should we support 150% ?
310 if (vol.ToScalarAvg () < 0)
311 vol.Set (0);
312 else if (vol.ToScalarAvg () > 1)
313 vol.Set (1);
314
315 CurrentSink.SetVolume (vol, (int success) => {});
316 }
317
318 void PulseaudioSetVolume (double val)
319 {
320 if (!IsPulseAudioAvailable)
321 return;
322
323 Pulseaudio.Volume vol = CurrentSink.Volume.Copy ();
324 vol.Set (val);
325
326 //TODO should we support 150% ?
327 if (vol.ToScalarAvg () < 0)
328 vol.Set (0);
329 else if (vol.ToScalarAvg () > 1)
330 vol.Set (1);
331
332 CurrentSink.SetVolume (vol, (int success) => {});
333 }
334
335 void PulseaudioSetSinkInputVolume (SinkInput sinkinput, double val)
336 {
337 if (!IsPulseAudioAvailable)
338 return;
339
340 Pulseaudio.Volume vol = sinkinput.Volume.Copy ();
341 vol.Set (val);
342
343 //TODO should we support 150% ?
344 if (vol.ToScalarAvg () < 0)
345 vol.Set (0);
346 else if (vol.ToScalarAvg () > 1)
347 vol.Set (1);
348
349 sinkinput.SetVolume (vol, (int success) => {});
350 }
351
352 string GetVolumeIcon (double volume, bool muted)
353 {
354 //audio-output-none-panel, audio-volume-high-panel, audio-volume-low-panel, audio-volume-low-zero-panel,
355 //audio-volume-medium-panel, audio-volume-muted-blocked-panel, audio-volume-muted-panel
356 if (!PASinks.Any ())
357 return "audio-output-none-panel;;audio-volume-muted";
358
359 if (muted)
360 return "audio-volume-muted-panel;;audio-volume-muted";
361 else if (volume <= 0.05)
362 return "audio-volume-low-zero-panel;;audio-volume-muted";
363 else if (volume < 0.2)
364 return "audio-volume-low-panel;;audio-volume-low";
365 else if (volume < 0.6)
366 return "audio-volume-medium-panel;;audio-volume-medium";
367 else
368 return "audio-volume-high-panel;;audio-volume-high";
369 }
370
371 protected override void OnScrolled (Gdk.ScrollDirection direction, Gdk.ModifierType mod)
372 {
373 if (IsPulseAudioAvailable) {
374 if (direction == Gdk.ScrollDirection.Up) {
375 PulseaudioChangeVolume (0.03);
376 } else if (direction == Gdk.ScrollDirection.Down) {
377 PulseaudioChangeVolume (-0.03);
378 }
379 } else {
380 if (direction == Gdk.ScrollDirection.Up) {
381 SimpleExecute (AlsaCommandVolumeUp);
382 } else if (direction == Gdk.ScrollDirection.Down) {
383 SimpleExecute (AlsaCommandVolumeDown);
384 }
385 }
386 }
387
388 public override MenuButton MenuButton {
389 get { return MenuButton.Left | MenuButton.Right; }
390 }
391
392 protected override ClickAnimation OnClicked (uint button, Gdk.ModifierType mod, double xPercent, double yPercent)
393 {
394 if (button == 1 && IsPulseAudioAvailable) {
395 //TODO mute and unmute all sinks
396 pa_context.SetSinkMute (CurrentSink.Index, !CurrentSink.Mute, (int success) => {});
397 }
398 return ClickAnimation.None;
399 }
400
401 protected override MenuList OnGetMenuItems ()
402 {
403 MenuList list = new MenuList ();
404
405 list[MenuListContainer.Header].Add (new MenuItem (Catalog.GetString ("Sound Preferences..."), "gnome-volume-control;;multimedia-volume-control", (o, a) => { DockServices.System.Execute ("gnome-volume-control"); }));
406
407 if (IsPulseAudioAvailable) {
408 // TODO only show them optionally
409 // show Output-Devices if there are more than one
410 if (PASinks.Count () > 1) {
411 list.SetContainerTitle (MenuListContainer.Actions, Catalog.GetString ("Available Outputs"));
412 foreach (Sink sink in PASinks) {
413 Sink s = sink;
414 MenuItem m;
415 if (CurrentSink == s)
416 m = new MenuItem (s.Description, "gtk-apply");
417 else
418 m = new MenuItem (s.Description, "", (o, a) => { CurrentSink = s; });
419 list[MenuListContainer.Actions].Add (m);
420 }
421 }
422
423 // add Client list
424 if (PAClients.Any ()) {
425 //list.SetContainerTitle (MenuListContainer.RelatedItems, Catalog.GetString ("Playing Applications"));
426 foreach (Client client in UsableClients.OrderBy (c => c.Name)) {
427 // get DesktopItem from WindowMatcher for this PID
428 //int pid = int.Parse (pl[Properties.ApplicationPID]);
429 //DesktopItem di = null;
430 //Wnck.Window w = null;
431 //foreach (Wnck.Window window in Wnck.Screen.Default.Windows)
432 // if (window.Pid == pid) {
433 //di = WindowMatcher.Default.DesktopItemForWindow (window);
434 // w = window;
435 // break;
436 // }
437
438 string icon = client.Properties[Properties.ApplicationIconName];
439 if (string.IsNullOrEmpty (icon))
440 icon = "applications-multimedia";
441
442 foreach (SinkInput sinkinput in UsableSinkInputs.FindAll (si => si.Client == client.Index).OrderBy (si => si.Name)) {
443 SinkInput si = sinkinput;
444 list[MenuListContainer.RelatedItems].Add (new SliderMenuItem (string.IsNullOrEmpty (si.Name) ? client.Name : string.Format ("{0} ({1})", client.Name, si.Name),
445 si.Volume.ToScalarAvg (), icon,
446 (o, e) => {
447 PulseaudioSetSinkInputVolume (si, e.NewPosition);
448 }));
449 }
450 }
451 }
452
453 // add master volume control
454 //TODO mute and unmute all sinks ?
455 if (CurrentSink != null) {
456 list[MenuListContainer.Footer].Add (new MenuItem ((CurrentSink.Mute ? Catalog.GetString ("Unmute") : Catalog.GetString ("Mute")),
457 "" ,
458 (o, a) => {
459 pa_context.SetSinkMute (CurrentSink.Index, !CurrentSink.Mute, (int success) => {});
460 }));
461 list[MenuListContainer.Footer].Add (new SliderMenuItem ("", CurrentSink.Volume.ToScalarAvg (),
462 GetVolumeIcon (CurrentSink.Volume.ToScalarAvg (), CurrentSink.Mute),
463 (o, e) => {
464 (o as MenuItem).Icon = GetVolumeIcon (e.NewPosition, (CurrentSink != null ? CurrentSink.Mute : true));
465 PulseaudioSetVolume (e.NewPosition);
466 }));
467 }
468 } else {
469 //TODO Alsa fallback
470 }
471
472 return list;
473 }
474
475 public void SimpleExecute (string executable)
476 {
477 try {
478 Log<SystemService>.Debug ("Calling: " + executable);
479 System.Diagnostics.Process proc = System.Diagnostics.Process.Start (executable);
480 proc.Dispose ();
481 } catch {
482 Log<SystemService>.Error ("Error executing '" + executable + "'");
483 }
484 }
485
486 #region Pulseaudio helper
487 public static void RunUntilEventSignal (Action action, EventWaitHandle until, string timeoutMessage)
488 {
489 var timeout = new EventWaitHandle (false, EventResetMode.AutoReset);
490 GLib.Timeout.Add (1000, () => {
491 timeout.Set ();
492 return false;
493 });
494 action ();
495 while (!until.WaitOne (0, true)) {
496 DrainEventLoop ();
497 if (timeout.WaitOne (0, true)) {
498 Log<SoundDockItem>.Error (timeoutMessage);
499 }
500 }
501 }
502
503 public static void DrainEventLoop ()
504 {
505 while (GLib.MainContext.Iteration (false)) {}
506 }
507 #endregion
508
509 #region IDisposable implementation
510 public override void Dispose ()
511 {
512 PAClients.ForEach (c => c.Dispose ());
513 PAClients.Clear ();
514
515 PASinkInputs.ForEach (si => si.Dispose ());
516 PASinkInputs.Clear ();
517
518 PASinks.ForEach (s => {
519 s.VolumeChanged -= HandlePulseaudioSinkVolumeChanged;
520 s.Dispose ();
521 });
522 PASinks.Clear ();
523
524 CurrentSink = null;
525
526 pa_context.Ready -= HandlePulseaudioContextReady;
527 pa_context.SinkEvent -= HandlePulseaudioContextSinkEvent;
528 pa_context.SinkInputEvent -= HandlePulseaudioContextSinkInputEvent;
529 pa_context.ClientEvent -= HandlePulseaudioContextClientEvent;
530 pa_context.Dispose (); // all handlers are getting removed
531
532 base.Dispose ();
533 }
534
535 #endregion
536 }
537}
0538
=== added file 'StandardPlugins/Sound/src/SoundItemProvider.cs'
--- StandardPlugins/Sound/src/SoundItemProvider.cs 1970-01-01 00:00:00 +0000
+++ StandardPlugins/Sound/src/SoundItemProvider.cs 2012-02-04 07:53:22 +0000
@@ -0,0 +1,42 @@
1//
2// Copyright (C) 2011 Rico Tzschichholz
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//
17
18using System;
19using System.Collections.Generic;
20
21using Docky.Items;
22
23namespace Sound
24{
25 public class SoundItemProvider : AbstractDockItemProvider
26 {
27 #region IDockItemProvider implementation
28
29 public override string Name {
30 get {
31 return "Sound";
32 }
33 }
34
35 #endregion
36
37 public SoundItemProvider ()
38 {
39 Items = new SoundDockItem ().AsSingle<AbstractDockItem> ();
40 }
41 }
42}
043
=== modified file 'configure.ac'
--- configure.ac 2012-01-31 12:16:30 +0000
+++ configure.ac 2012-02-04 07:53:22 +0000
@@ -207,6 +207,8 @@
207StandardPlugins/RecentDocuments/Resources/RecentDocuments.addin.xml207StandardPlugins/RecentDocuments/Resources/RecentDocuments.addin.xml
208StandardPlugins/SessionManager/Makefile208StandardPlugins/SessionManager/Makefile
209StandardPlugins/SessionManager/Resources/SessionManager.addin.xml209StandardPlugins/SessionManager/Resources/SessionManager.addin.xml
210StandardPlugins/Sound/Makefile
211StandardPlugins/Sound/Resources/Sound.addin.xml
210StandardPlugins/Timer/Makefile212StandardPlugins/Timer/Makefile
211StandardPlugins/Timer/Resources/Timer.addin.xml213StandardPlugins/Timer/Resources/Timer.addin.xml
212StandardPlugins/Trash/Makefile214StandardPlugins/Trash/Makefile
213215
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2011-03-02 23:41:41 +0000
+++ po/POTFILES.in 2012-02-04 07:53:22 +0000
@@ -42,6 +42,7 @@
42StandardPlugins/NPR/src/StationDockItem.cs42StandardPlugins/NPR/src/StationDockItem.cs
43StandardPlugins/RecentDocuments/src/RecentFilesProvider.cs43StandardPlugins/RecentDocuments/src/RecentFilesProvider.cs
44StandardPlugins/SessionManager/src/SessionActionsProvider.cs44StandardPlugins/SessionManager/src/SessionActionsProvider.cs
45StandardPlugins/Sound/src/SoundDockItem.cs
45StandardPlugins/Timer/src/TimerDockItem.cs46StandardPlugins/Timer/src/TimerDockItem.cs
46StandardPlugins/Timer/src/TimerMainDockItem.cs47StandardPlugins/Timer/src/TimerMainDockItem.cs
47StandardPlugins/Trash/src/TrashDockItem.cs48StandardPlugins/Trash/src/TrashDockItem.cs
4849
=== modified file 'po/docky.pot'
--- po/docky.pot 2011-06-17 11:50:40 +0000
+++ po/docky.pot 2012-02-04 07:53:22 +0000
@@ -1274,6 +1274,36 @@
1274msgid "Are you sure you want to close all programs and shut down the computer?"1274msgid "Are you sure you want to close all programs and shut down the computer?"
1275msgstr ""1275msgstr ""
12761276
1277#: ../StandardPlugins/Sound/src/SoundDockItem.cs:131
1278#: ../StandardPlugins/Sound/src/SoundDockItem.cs:290
1279msgid "Volume Control"
1280msgstr ""
1281
1282#: ../StandardPlugins/Sound/src/SoundDockItem.cs:297
1283#, csharp-format
1284msgid "Volume {0}%"
1285msgstr ""
1286
1287#: ../StandardPlugins/Sound/src/SoundDockItem.cs:298
1288msgid "Muted"
1289msgstr ""
1290
1291#: ../StandardPlugins/Sound/src/SoundDockItem.cs:405
1292msgid "Sound Preferences..."
1293msgstr ""
1294
1295#: ../StandardPlugins/Sound/src/SoundDockItem.cs:411
1296msgid "Available Outputs"
1297msgstr ""
1298
1299#: ../StandardPlugins/Sound/src/SoundDockItem.cs:452
1300msgid "Unmute"
1301msgstr ""
1302
1303#: ../StandardPlugins/Sound/src/SoundDockItem.cs:452
1304msgid "Mute"
1305msgstr ""
1306
1277#: ../StandardPlugins/Timer/src/TimerDockItem.cs:861307#: ../StandardPlugins/Timer/src/TimerDockItem.cs:86
1278#, csharp-format1308#, csharp-format
1279msgid "A timer set for {0} has expired."1309msgid "A timer set for {0} has expired."

Subscribers

People subscribed via source and target branches

to status/vote changes: