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