[SCM] earcandy packaging branch, upstream, updated. upstream/0.5-1-gabb2267
fladi-guest at users.alioth.debian.org
fladi-guest at users.alioth.debian.org
Wed Sep 1 10:34:11 UTC 2010
The following commit has been merged in the upstream branch:
commit abb226744082c90aa3c7dcfdff0bc71090e49f44
Author: Fladischer Michael <FladischerMichael at fladi.at>
Date: Fri Aug 27 21:51:12 2010 +0200
Imported Upstream version 0.9
diff --git a/.quickly b/.quickly
new file mode 100644
index 0000000..e14aa10
--- /dev/null
+++ b/.quickly
@@ -0,0 +1,3 @@
+project = earcandy
+template = ubuntu-project
+format = 0.2.5
diff --git a/COPYING b/COPYING
deleted file mode 100644
index 1bb399f..0000000
--- a/COPYING
+++ /dev/null
@@ -1,341 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
-Public License instead of this License.
-
diff --git a/Copyright b/Copyright
new file mode 100644
index 0000000..659103a
--- /dev/null
+++ b/Copyright
@@ -0,0 +1,3 @@
+# Copyright (C) YYYY <Your Name> <Your E-mail>
+### BEGIN AUTOMATIC LICENSE GENERATION
+### END AUTOMATIC LICENSE GENERATION
diff --git a/README b/README
deleted file mode 100644
index ba3eda7..0000000
--- a/README
+++ /dev/null
@@ -1,4 +0,0 @@
-Ear Candy is a sound manager that controls pulseaudio to create a seamless sound experience
-
-Please note that much of the functionality should eventually make its way into pulseaudio its self
-
diff --git a/bin/earcandy b/bin/earcandy
new file mode 100755
index 0000000..a978b40
--- /dev/null
+++ b/bin/earcandy
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+### BEGIN LICENSE
+# This file is in the public domain
+### END LICENSE
+
+import sys
+import os
+import gtk
+import logging
+
+# Check if we are working in the source tree or from the installed
+# package and mangle the python path accordingly
+if os.path.dirname(sys.argv[0]) != ".":
+ if sys.argv[0][0] == "/":
+ fullPath = os.path.dirname(sys.argv[0])
+ else:
+ fullPath = os.getcwd() + "/" + os.path.dirname(sys.argv[0])
+else:
+ fullPath = os.getcwd()
+sys.path.insert(0, os.path.dirname(fullPath))
+
+from earcandy.earcandyconfig import getdatapath
+from earcandy.EarCandy import EarCandy
+
+if __name__ == "__main__":
+
+ #run the application
+ logging.basicConfig(level=logging.INFO)
+
+ gtk.window_set_default_icon_from_file(os.path.join(getdatapath(), 'media', 'icon.png'))
+
+ # Turn on gtk threading
+ gtk.gdk.threads_init()
+
+ ec = EarCandy()
+ ec.run()
+
+ gtk.main()
diff --git a/files/settings.xml b/data/defaults/settings.xml
similarity index 96%
rename from files/settings.xml
rename to data/defaults/settings.xml
index 1c449ce..a4ab937 100644
--- a/files/settings.xml
+++ b/data/defaults/settings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
- <earcandy fade_timer_speed="0.1" follow_new_outputs="True" tray_visible="True" version="0.5">
+ <earcandy fade_timer_speed="0.015" follow_new_outputs="True" tray_visible="True" version="0.6">
<rules>
<rule apply_volume_meter_hack="True" category="music" description="File Manager" fade_volume="True" icon_name="system-file-manager" name="totem-audio-preview" rule_re_application="nautilus" rule_re_command=".*\/nautilus" rule_re_window_title="" volume_default="100" volume_mute="-1" window_position_fade="True" />
<rule apply_volume_meter_hack="True" category="video" description="Totem Movie Player" fade_volume="True" icon_name="totem" name="Totem Movie Player" rule_re_application="totem movie player" rule_re_command=".*\/totem movie player" rule_re_window_title="" volume_default="100" volume_mute="-1" window_position_fade="True"/>
diff --git a/data/media/earcandy.png b/data/media/earcandy.png
new file mode 100644
index 0000000..9b18fb7
Binary files /dev/null and b/data/media/earcandy.png differ
diff --git a/data/media/icon.png b/data/media/icon.png
new file mode 100644
index 0000000..9b18fb7
Binary files /dev/null and b/data/media/icon.png differ
diff --git a/data/ui/AboutEarcandyDialog.ui b/data/ui/AboutEarcandyDialog.ui
new file mode 100644
index 0000000..939e039
--- /dev/null
+++ b/data/ui/AboutEarcandyDialog.ui
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-requires about_earcandy_dialog 1.0 -->
+ <!-- interface-naming-policy project-wide -->
+ <object class="AboutEarcandyDialog" id="about_earcandy_dialog">
+ <property name="border_width">5</property>
+ <property name="icon">../media/icon.png</property>
+ <property name="type_hint">normal</property>
+ <property name="has_separator">False</property>
+ <property name="program_name">Earcandy</property>
+ <child internal-child="vbox">
+ <object class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="action_area">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">end</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/files/pulseoptions.glade b/data/ui/EarCandy.ui
similarity index 54%
rename from files/pulseoptions.glade
rename to data/ui/EarCandy.ui
index 72dcda3..ab9cd1b 100644
--- a/files/pulseoptions.glade
+++ b/data/ui/EarCandy.ui
@@ -1,372 +1,363 @@
<?xml version="1.0"?>
-<glade-interface>
- <!-- interface-requires gtk+ 2.16 -->
+<interface>
+ <requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
- <widget class="GtkDialog" id="dialog_options">
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="value">0.01</property>
+ <property name="lower">0.01</property>
+ <property name="upper">0.20000000000000001</property>
+ <property name="step_increment">0.01</property>
+ <property name="page_increment">0.01</property>
+ <property name="page_size">0.01</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="value">20</property>
+ <property name="upper">110</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">1</property>
+ <property name="page_size">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="value">50</property>
+ <property name="upper">110</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <property name="page_size">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="value">50</property>
+ <property name="upper">110</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <property name="page_size">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="value">50</property>
+ <property name="upper">110</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <property name="page_size">10</property>
+ </object>
+ <object class="GtkListStore" id="model1">
+ <columns>
+ <!-- column-name gchararray -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">[ automatic ]</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="dialog_options">
<property name="width_request">500</property>
<property name="height_request">400</property>
<property name="border_width">5</property>
- <property name="title" translatable="yes">Ear Candy</property>
+ <property name="title" translatable="yes">Ear Candy - Audio Clients</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
- <widget class="GtkVBox" id="dialog-vbox1">
+ <object class="GtkVBox" id="dialog-vbox1">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
- <widget class="GtkNotebook" id="notebook1">
+ <object class="GtkNotebook" id="notebook1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
- <widget class="GtkVBox" id="vbox1">
+ <object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<child>
- <widget class="GtkHBox" id="hbox1">
- <property name="visible">True</property>
- <child>
- <widget class="GtkToolbar" id="toolbar1">
- <property name="visible">True</property>
- <property name="toolbar_style">both</property>
- <child>
- <widget class="GtkToolButton" id="toolbutton_add">
- <property name="visible">True</property>
- <property name="stock_id">gtk-add</property>
- <signal name="clicked" handler="on_button_add_new_clicked"/>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
- <child>
- <widget class="GtkToolButton" id="toolbutton_edit">
- <property name="visible">True</property>
- <property name="use_underline">True</property>
- <property name="stock_id">gtk-edit</property>
- <signal name="clicked" handler="on_toolbutton_edit_clicked" after="yes"/>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
- <child>
- <widget class="GtkToolButton" id="toolbutton_delete">
- <property name="visible">True</property>
- <property name="stock_id">gtk-delete</property>
- <signal name="clicked" handler="on_toolbutton_delete_clicked"/>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="position">0</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">0</property>
- </packing>
+ <placeholder/>
</child>
<child>
- <widget class="GtkHBox" id="hbox3">
+ <object class="GtkHBox" id="hbox3">
<property name="visible">True</property>
<child>
- <widget class="GtkScrolledWindow" id="scrolledwindow1">
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
- <property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">etched-in</property>
<child>
- <widget class="GtkTreeView" id="treeview_pulse_clients">
+ <object class="GtkViewport" id="viewport1">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="headers_clickable">False</property>
- <signal name="cursor_changed" handler="on_treeview_pulse_clients_cursor_changed"/>
- <signal name="row_activated" handler="on_treeview_pulse_clients_row_activated"/>
- </widget>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkVBox" id="vbox_repeater">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
- <child>
- <widget class="GtkHBox" id="hbox5">
- <property name="visible">True</property>
- <child>
- <widget class="GtkAlignment" id="alignment1">
- <property name="visible">True</property>
- <property name="xalign">1</property>
- <property name="xscale">0</property>
- <child>
- <widget class="GtkLabel" id="label3">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Display :</property>
- </widget>
- </child>
- </widget>
- <packing>
- <property name="padding">4</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <widget class="GtkComboBox" id="combobox_view">
- <property name="visible">True</property>
- <property name="active">0</property>
- <property name="items" translatable="yes">Only active rules
-All rules</property>
- <signal name="changed" handler="on_combobox_view_changed"/>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="padding">4</property>
- <property name="position">1</property>
- </packing>
- </child>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="padding">4</property>
- <property name="position">2</property>
- </packing>
- </child>
- </widget>
+ </object>
</child>
- <child>
- <widget class="GtkLabel" id="label2">
+ <child type="tab">
+ <object class="GtkLabel" id="label2">
<property name="visible">True</property>
- <property name="label" translatable="yes">Application Rules</property>
- </widget>
+ <property name="label" translatable="yes">Applications</property>
+ </object>
<packing>
+ <property name="position">1</property>
<property name="tab_fill">False</property>
- <property name="type">tab</property>
</packing>
</child>
<child>
- <widget class="GtkHBox" id="hbox2">
+ <object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
- <widget class="GtkTable" id="table3">
+ <object class="GtkTable" id="table3">
<property name="visible">True</property>
- <property name="n_rows">7</property>
+ <property name="n_rows">11</property>
<property name="n_columns">2</property>
<child>
- <widget class="GtkAlignment" id="alignment6">
+ <object class="GtkAlignment" id="alignment6">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkCheckButton" id="checkbutton_tray">
+ <object class="GtkCheckButton" id="checkbutton_tray">
<property name="label" translatable="yes">Show Icon</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_checkbutton_tray_toggled"/>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label10">
+ <object class="GtkHScale" id="hscale_fade">
<property name="visible">True</property>
- <property name="xalign">0</property>
- <property name="label" translatable="yes">Notification Tray</property>
- </widget>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment1</property>
+ <property name="inverted">True</property>
+ <property name="digits">2</property>
+ <property name="draw_value">False</property>
+ <property name="value_pos">right</property>
+ <signal name="value_changed" handler="on_hscale_fade_value_changed"/>
+ </object>
<packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
- <property name="x_options"></property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label6">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Volume Fade Speed</property>
- </widget>
+ <object class="GtkHScale" id="hscale_mute_level">
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="digits">0</property>
+ <property name="value_pos">right</property>
+ <signal name="value_changed" handler="on_hscale_mute_level_value_changed"/>
+ <signal name="change_value" handler="on_hscale_mute_level_change_value"/>
+ </object>
<packing>
- <property name="x_options"></property>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkHScale" id="hscale_fade">
+ <object class="GtkCheckButton" id="checkbutton_autostart">
+ <property name="label" translatable="yes">Start on login</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
- <property name="adjustment">0.01 0.01 0.20000000000000001 0.01 0.01 0.01</property>
- <property name="inverted">True</property>
- <property name="digits">2</property>
- <property name="draw_value">False</property>
- <property name="value_pos">right</property>
- <signal name="value_changed" handler="on_hscale_fade_value_changed"/>
- </widget>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_checkbutton_autostart_toggled"/>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label9">
- <property name="label" translatable="yes">Partial Mute Level</property>
- </widget>
+ <object class="GtkCheckButton" id="checkbutton_mute_phone">
+ <property name="label" translatable="yes">Include 'phone' category when muting</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_checkbutton_mute_phone_toggled"/>
+ </object>
<packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="x_options"></property>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkHScale" id="hscale_mute_level">
+ <object class="GtkExpander" id="expander1">
<property name="can_focus">True</property>
- <property name="adjustment">20 0 110 1 1 1</property>
- <property name="digits">0</property>
- <property name="value_pos">right</property>
- <signal name="value_changed" handler="on_hscale_mute_level_value_changed"/>
- <signal name="change_value" handler="on_hscale_mute_level_change_value"/>
- </widget>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">4</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="vscrollbar_policy">automatic</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_plugins">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Application control plugins</property>
+ </object>
+ </child>
+ </object>
<packing>
- <property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
+ <property name="top_attach">9</property>
+ <property name="bottom_attach">10</property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Cross Fade Speed</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label11">
+ <object class="GtkAlignment" id="alignment7">
<property name="visible">True</property>
- <property name="label" translatable="yes">Output Device</property>
- </widget>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="label" translatable="yes">Partial Mute Level</property>
+ </object>
+ </child>
+ </object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
- <property name="x_options"></property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment7">
- <property name="visible">True</property>
+ <object class="GtkAlignment" id="alignment8">
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkCheckButton" id="checkbutton_output">
- <property name="label" translatable="yes">Move streams to new output (USB Headsets)</property>
+ <object class="GtkLabel" id="label5">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_checkbutton_output_toggled"/>
- </widget>
+ <property name="xalign">0.49000000953674316</property>
+ <property name="label" translatable="yes">Mute</property>
+ </object>
</child>
- </widget>
+ </object>
<packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label15">
- <property name="label" translatable="yes">Reset volume</property>
- </widget>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Notification Tray</property>
+ </object>
+ </child>
+ </object>
<packing>
- <property name="top_attach">6</property>
- <property name="bottom_attach">7</property>
- <property name="x_options"></property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment3">
+ <object class="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkButton" id="button_reset">
+ <object class="GtkLabel" id="label4">
<property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">True</property>
- <signal name="clicked" handler="on_button_reset_clicked"/>
- <child>
- <widget class="GtkHBox" id="hbox4">
- <property name="visible">True</property>
- <child>
- <widget class="GtkImage" id="image1">
- <property name="visible">True</property>
- <property name="stock">gtk-refresh</property>
- <property name="icon-size">2</property>
- </widget>
- <packing>
- <property name="padding">4</property>
- <property name="position">0</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="label17">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Reset all volume levels</property>
- </widget>
- <packing>
- <property name="padding">4</property>
- <property name="position">1</property>
- </packing>
- </child>
- </widget>
- </child>
- </widget>
+ <property name="label" translatable="yes">Autostart</property>
+ </object>
</child>
- </widget>
+ </object>
<packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
<property name="y_options"></property>
@@ -375,121 +366,173 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label4">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Autostart</property>
- </widget>
+ <object class="GtkFrame" id="frame1">
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkTable" id="table1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="height_request">50</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">automatic</property>
+ <property name="shadow_type">etched-in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_outputs">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <property name="reorderable">True</property>
+ <signal name="drag_end" handler="on_treeview_outputs_drag_drop"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="x_padding">8</property>
+ <property name="y_padding">8</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"> Output Devices </property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
<packing>
- <property name="top_attach">4</property>
- <property name="bottom_attach">5</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">10</property>
+ <property name="bottom_attach">11</property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkCheckButton" id="checkbutton_autostart">
- <property name="label" translatable="yes">Start on login</property>
- <property name="visible">True</property>
+ <object class="GtkButton" id="button1">
+ <property name="label">gtk-revert-to-saved</property>
<property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="draw_indicator">True</property>
- <signal name="toggled" handler="on_checkbutton_autostart_toggled"/>
- </widget>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
+ <signal name="clicked" handler="on_button_reset_clicked"/>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
- <property name="top_attach">4</property>
- <property name="bottom_attach">5</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
<child>
- <widget class="GtkVBox" id="vbox2">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="xalign">0</property>
+ <property name="xscale">0</property>
<child>
- <widget class="GtkHSeparator" id="hseparator1"/>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="padding">4</property>
- <property name="position">0</property>
- </packing>
+ <object class="GtkLabel" id="label7">
+ <property name="label" translatable="yes">Restore defaults</property>
+ </object>
</child>
- </widget>
+ </object>
<packing>
- <property name="right_attach">2</property>
- <property name="top_attach">5</property>
- <property name="bottom_attach">6</property>
+ <property name="top_attach">7</property>
+ <property name="bottom_attach">8</property>
<property name="y_options"></property>
<property name="x_padding">4</property>
<property name="y_padding">4</property>
</packing>
</child>
- </widget>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
<packing>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="position">1</property>
</packing>
</child>
- <child>
- <widget class="GtkLabel" id="label1">
+ <child type="tab">
+ <object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Advanced</property>
- </widget>
+ </object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
- <property name="type">tab</property>
</packing>
</child>
- </widget>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
- <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <object class="GtkHButtonBox" id="dialog-action_area1">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
- <widget class="GtkButton" id="button_close">
- <property name="label" translatable="yes">gtk-close</property>
+ <object class="GtkButton" id="button_close">
+ <property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_close_button_clicked"/>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
- </widget>
+ </object>
</child>
- </widget>
- <widget class="GtkDialog" id="dialog_select">
+ <action-widgets>
+ <action-widget response="0">button_close</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkDialog" id="dialog_select">
<property name="border_width">5</property>
<property name="title" translatable="yes">Select PulseAudio Stream</property>
<property name="modal">True</property>
@@ -497,25 +540,25 @@ All rules</property>
<property name="type_hint">dialog</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
- <widget class="GtkVBox" id="dialog-vbox2">
+ <object class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="spacing">2</property>
<child>
- <widget class="GtkVBox" id="vbox3">
+ <object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<child>
- <widget class="GtkAlignment" id="alignment3">
+ <object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkLabel" id="label16">
+ <object class="GtkLabel" id="label16">
<property name="visible">True</property>
<property name="label" translatable="yes"><b>1) Select pulseaudio client</b></property>
<property name="use_markup">True</property>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -524,10 +567,9 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkComboBox" id="combobox_clients">
+ <object class="GtkComboBox" id="combobox_clients">
<property name="visible">True</property>
- <property name="items" translatable="yes"></property>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -535,11 +577,11 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label14">
+ <object class="GtkLabel" id="label14">
<property name="visible">True</property>
<property name="label" translatable="yes"><i>For a client to show here it must have played a sound at least once</i></property>
<property name="use_markup">True</property>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -548,18 +590,18 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment4">
+ <object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkLabel" id="label4">
+ <object class="GtkLabel" id="label400">
<property name="visible">True</property>
<property name="label" translatable="yes"><b>2) Select application</b></property>
<property name="use_markup">True</property>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -568,30 +610,30 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkScrolledWindow" id="scrolledwindow2">
+ <object class="GtkScrolledWindow" id="scrolledwindow200">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">etched-in</property>
<child>
- <widget class="GtkTreeView" id="treeview_applications">
+ <object class="GtkTreeView" id="treeview_applications">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label8">
+ <object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes"><i>For an application to show here it must be open</i></property>
<property name="use_markup">True</property>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -600,18 +642,18 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment5">
+ <object class="GtkAlignment" id="alignment500">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkLabel" id="label5">
+ <object class="GtkLabel" id="label500">
<property name="visible">True</property>
<property name="label" translatable="yes"><b>3) Select category</b></property>
<property name="use_markup">True</property>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -620,35 +662,34 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkComboBox" id="combobox_category">
+ <object class="GtkComboBox" id="combobox_category">
<property name="visible">True</property>
- <property name="items" translatable="yes"></property>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">7</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
<child internal-child="action_area">
- <widget class="GtkHButtonBox" id="dialog-action_area2">
+ <object class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
- <widget class="GtkButton" id="button_cancel">
- <property name="label" translatable="yes">gtk-cancel</property>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_button_cancel_clicked"/>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -656,31 +697,35 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkButton" id="button_add">
- <property name="label" translatable="yes">gtk-add</property>
+ <object class="GtkButton" id="button_add">
+ <property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_button_add_clicked"/>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
- </widget>
+ </object>
</child>
- </widget>
- <widget class="GtkWindow" id="popup_volume_control">
+ <action-widgets>
+ <action-widget response="0">button_cancel</action-widget>
+ <action-widget response="0">button_add</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkWindow" id="popup_volume_control">
<property name="height_request">150</property>
<property name="can_focus">True</property>
<property name="has_focus">True</property>
@@ -694,345 +739,400 @@ All rules</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">popup-menu</property>
<child>
- <widget class="GtkHBox" id="hbox4">
+ <object class="GtkHBox" id="hbox4">
<property name="visible">True</property>
<child>
- <widget class="GtkVBox" id="vbox5">
+ <object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
- <widget class="GtkButton" id="button_suspend">
+ <object class="GtkButton" id="button_mute">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
- <property name="tooltip" translatable="yes">Lock volume levels</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Mute sound</property>
+ <signal name="clicked" handler="on_button_mute_clicked"/>
+ <child>
+ <object class="GtkImage" id="image_mute">
+ <property name="visible">True</property>
+ <property name="icon_name">audio-volume-muted</property>
+ <property name="icon-size">2</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">4</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_suspend">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Lock volume levels</property>
<signal name="clicked" handler="on_button_suspend_clicked"/>
<child>
- <widget class="GtkImage" id="image_status">
+ <object class="GtkImage" id="image_status">
<property name="visible">True</property>
<property name="stock">gtk-media-pause</property>
<property name="icon-size">2</property>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="padding">4</property>
- <property name="position">0</property>
+ <property name="position">1</property>
</packing>
</child>
<child>
- <widget class="GtkVScale" id="vscale_volume">
+ <object class="GtkVScale" id="vscale_volume">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="orientation">vertical</property>
- <property name="adjustment">50 0 110 1 10 10</property>
+ <property name="adjustment">adjustment3</property>
<property name="inverted">True</property>
<property name="digits">0</property>
<property name="draw_value">False</property>
<property name="value_pos">bottom</property>
<signal name="value_changed" handler="on_vscale_volume_value_changed"/>
<signal name="change_value" handler="on_vscale_volume_change_value"/>
- </widget>
+ </object>
<packing>
<property name="padding">4</property>
- <property name="position">1</property>
+ <property name="position">2</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
- </widget>
+ </object>
</child>
- </widget>
- <widget class="GtkWindow" id="window_client_properties">
+ </object>
+ <object class="GtkWindow" id="window_client_properties">
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<child>
- <widget class="GtkHBox" id="hbox1">
+ <object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
- <widget class="GtkVBox" id="vbox1">
+ <object class="GtkVBox" id="vbox100">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
- <widget class="GtkHBox" id="hbox2">
+ <object class="GtkVBox" id="vbox300">
<property name="visible">True</property>
+ <property name="orientation">vertical</property>
<child>
- <widget class="GtkImage" id="image_icon">
+ <object class="GtkHBox" id="hbox200">
<property name="visible">True</property>
- <property name="stock">gtk-directory</property>
- <property name="icon-size">6</property>
- </widget>
+ <child>
+ <object class="GtkImage" id="image_icon">
+ <property name="visible">True</property>
+ <property name="stock">gtk-directory</property>
+ <property name="icon-size">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">4</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes"><big>Banshee</big></property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">4</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
<packing>
- <property name="expand">False</property>
+ <property name="fill">False</property>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label_name">
+ <object class="GtkTable" id="table100">
<property name="visible">True</property>
- <property name="label" translatable="yes"><big>Banshee</big></property>
- <property name="use_markup">True</property>
- </widget>
+ <property name="n_rows">7</property>
+ <property name="n_columns">2</property>
+ <child>
+ <object class="GtkLabel" id="label_description">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Description</property>
+ </object>
+ <packing>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name2">
+ <property name="label" translatable="yes">Category</property>
+ </object>
+ <packing>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">3</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox_category0"/>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_description">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_fix">
+ <property name="label" translatable="yes">Use volume meter detection</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Activity</property>
+ </object>
+ <packing>
+ <property name="top_attach">2</property>
+ <property name="bottom_attach">3</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">3</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Fade</property>
+ </object>
+ <packing>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">3</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_fade_volume">
+ <property name="label" translatable="yes">Fade volume</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="xalign">0.56999999284744263</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">3</property>
+ <property name="bottom_attach">4</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label60">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Output Device</property>
+ </object>
+ <packing>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox_output">
+ <property name="visible">True</property>
+ <property name="model">model1</property>
+ <property name="active">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="renderer1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">4</property>
+ <property name="bottom_attach">5</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Max</property>
+ </object>
+ <packing>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="hscale_max_volume">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment4</property>
+ <property name="digits">0</property>
+ <property name="value_pos">left</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">5</property>
+ <property name="bottom_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name5">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Min</property>
+ </object>
+ <packing>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkHScale" id="hscale_min_volume">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="adjustment">adjustment5</property>
+ <property name="digits">0</property>
+ <property name="value_pos">left</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="top_attach">6</property>
+ <property name="bottom_attach">7</property>
+ </packing>
+ </child>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
- <property name="padding">4</property>
<property name="position">1</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
+ <property name="expand">False</property>
<property name="fill">False</property>
- <property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
- <widget class="GtkTable" id="table1">
- <property name="visible">True</property>
- <property name="n_rows">7</property>
- <property name="n_columns">2</property>
- <child>
- <widget class="GtkLabel" id="label_description">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Description</property>
- </widget>
- <packing>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="label_name1">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Max Volume</property>
- </widget>
- <packing>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkHScale" id="hscale_max_volume">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="adjustment">100 0 110 1 10 10</property>
- <property name="digits">0</property>
- <property name="value_pos">left</property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">2</property>
- <property name="bottom_attach">3</property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="label_name2">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Category</property>
- </widget>
- <packing>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
- <property name="x_padding">3</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkComboBox" id="combobox_category">
- <property name="visible">True</property>
- <property name="items" translatable="yes"></property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">3</property>
- <property name="bottom_attach">4</property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkEntry" id="entry_description">
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="invisible_char">●</property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">1</property>
- <property name="bottom_attach">2</property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkCheckButton" id="checkbutton_fix">
- <property name="label" translatable="yes">Use volume meter detection</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="draw_indicator">True</property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">4</property>
- <property name="bottom_attach">5</property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="label_name3">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Fix</property>
- </widget>
- <packing>
- <property name="top_attach">4</property>
- <property name="bottom_attach">5</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
- <property name="x_padding">3</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="label_name4">
- <property name="visible">True</property>
- <property name="label" translatable="yes">Fade</property>
- </widget>
- <packing>
- <property name="top_attach">5</property>
- <property name="bottom_attach">6</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
- <property name="x_padding">3</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkCheckButton" id="checkbutton_fade_volume">
- <property name="label" translatable="yes">Fade volume</property>
- <property name="visible">True</property>
- <property name="can_focus">True</property>
- <property name="receives_default">False</property>
- <property name="xalign">0.56999999284744263</property>
- <property name="draw_indicator">True</property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">5</property>
- <property name="bottom_attach">6</property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkLabel" id="label6">
- <property name="label" translatable="yes">Output</property>
- </widget>
- <packing>
- <property name="top_attach">6</property>
- <property name="bottom_attach">7</property>
- <property name="x_options"></property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <widget class="GtkComboBox" id="combobox_output">
- <property name="active">0</property>
- <property name="items" translatable="yes">[ automatic ]</property>
- </widget>
- <packing>
- <property name="left_attach">1</property>
- <property name="right_attach">2</property>
- <property name="top_attach">6</property>
- <property name="bottom_attach">7</property>
- <property name="y_options"></property>
- <property name="x_padding">4</property>
- <property name="y_padding">4</property>
- </packing>
- </child>
- <child>
- <placeholder/>
- </child>
- <child>
- <placeholder/>
- </child>
- </widget>
- <packing>
- <property name="expand">False</property>
- <property name="fill">False</property>
- <property name="position">1</property>
- </packing>
+ <placeholder/>
</child>
<child>
- <widget class="GtkExpander" id="expander1">
+ <object class="GtkExpander" id="expander10">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
- <widget class="GtkVBox" id="vbox2">
+ <object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
- <widget class="GtkLabel" id="label5">
+ <object class="GtkLabel" id="label50">
<property name="visible">True</property>
<property name="label" translatable="yes"><b>Do not edit these values
unless you know what you are doing</b></property>
<property name="use_markup">True</property>
<property name="justify">center</property>
- </widget>
+ </object>
<packing>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
- <widget class="GtkTable" id="table2">
+ <object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<child>
- <widget class="GtkLabel" id="label2">
+ <object class="GtkLabel" id="label20">
<property name="visible">True</property>
<property name="label" translatable="yes">Window Title</property>
- </widget>
+ </object>
<packing>
<property name="x_options"></property>
<property name="y_options"></property>
@@ -1041,11 +1141,11 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkEntry" id="entry_window_title">
+ <object class="GtkEntry" id="entry_window_title">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- </widget>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
@@ -1055,10 +1155,10 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label3">
+ <object class="GtkLabel" id="label30">
<property name="visible">True</property>
<property name="label" translatable="yes">Command</property>
- </widget>
+ </object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
@@ -1069,10 +1169,10 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label4">
+ <object class="GtkLabel" id="label40">
<property name="visible">True</property>
<property name="label" translatable="yes">Application</property>
- </widget>
+ </object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
@@ -1083,11 +1183,11 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkEntry" id="entry_command">
+ <object class="GtkEntry" id="entry_command">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- </widget>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
@@ -1099,11 +1199,11 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkEntry" id="entry_application">
+ <object class="GtkEntry" id="entry_application">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- </widget>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
@@ -1115,10 +1215,10 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkLabel" id="label_description1">
+ <object class="GtkLabel" id="label_description1">
<property name="visible">True</property>
<property name="label" translatable="yes">Client Name</property>
- </widget>
+ </object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
@@ -1129,11 +1229,11 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkEntry" id="entry_name">
+ <object class="GtkEntry" id="entry_name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">●</property>
- </widget>
+ </object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
@@ -1144,23 +1244,20 @@ All rules</property>
<property name="y_padding">4</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="position">2</property>
</packing>
</child>
- </widget>
+ </object>
</child>
- <child>
- <widget class="GtkLabel" id="label1">
+ <child type="label">
+ <object class="GtkLabel" id="label101">
<property name="visible">True</property>
<property name="label" translatable="yes">Advanced client matching rules</property>
- </widget>
- <packing>
- <property name="type">label_item</property>
- </packing>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="padding">4</property>
@@ -1168,46 +1265,46 @@ All rules</property>
</packing>
</child>
<child>
- <widget class="GtkHBox" id="hbox3">
+ <object class="GtkHBox" id="hbox303">
<property name="visible">True</property>
<child>
- <widget class="GtkAlignment" id="alignment2">
+ <object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="xalign">1</property>
<property name="yalign">1</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkButton" id="button_close">
- <property name="label" translatable="yes">gtk-close</property>
+ <object class="GtkButton" id="button_close01">
+ <property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_button_close_clicked"/>
<signal name="activate" handler="on_button_close_activate"/>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
- <widget class="GtkAlignment" id="alignment1">
+ <object class="GtkAlignment" id="alignment101">
<property name="visible">True</property>
<property name="xalign">1</property>
<property name="xscale">0</property>
<child>
- <widget class="GtkButton" id="button_apply">
- <property name="label" translatable="yes">gtk-apply</property>
+ <object class="GtkButton" id="button_apply">
+ <property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_button_apply_clicked"/>
- </widget>
+ </object>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -1215,7 +1312,7 @@ All rules</property>
<property name="position">2</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
@@ -1223,13 +1320,108 @@ All rules</property>
<property name="position">3</property>
</packing>
</child>
- </widget>
+ </object>
<packing>
<property name="padding">4</property>
<property name="position">0</property>
</packing>
</child>
- </widget>
+ </object>
+ </child>
+ </object>
+ <object class="GtkWindow" id="window_client_holder">
+ <child>
+ <object class="GtkTable" id="table_clients">
+ <property name="visible">True</property>
+ <property name="n_rows">2</property>
+ <property name="n_columns">4</property>
+ <property name="column_spacing">2</property>
+ <property name="row_spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image_client_icon">
+ <property name="width_request">32</property>
+ <property name="visible">True</property>
+ <property name="stock">gtk-media-play</property>
+ <property name="icon-size">3</property>
+ </object>
+ <packing>
+ <property name="bottom_attach">2</property>
+ <property name="x_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progressbar_meter">
+ <property name="height_request">4</property>
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">4</property>
+ <property name="top_attach">1</property>
+ <property name="bottom_attach">2</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combobox_role">
+ <property name="visible">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="right_attach">3</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_client">
+ <property name="label" translatable="yes">Edit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">half</property>
+ <signal name="clicked" handler="on_button_client_clicked"/>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="right_attach">4</property>
+ <property name="x_options"></property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment303">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <child>
+ <object class="GtkLabel" id="label_client_name">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">label</property>
+ <property name="ellipsize">end</property>
+ <attributes>
+ <attribute name="gravity" value="west"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="right_attach">2</property>
+ <property name="y_options"></property>
+ <property name="x_padding">4</property>
+ <property name="y_padding">4</property>
+ </packing>
+ </child>
+ </object>
</child>
- </widget>
-</glade-interface>
+ </object>
+</interface>
diff --git a/data/ui/EarcandyWindow.ui b/data/ui/EarcandyWindow.ui
new file mode 100644
index 0000000..5176da1
--- /dev/null
+++ b/data/ui/EarcandyWindow.ui
@@ -0,0 +1,226 @@
+<?xml version="1.0"?>
+<interface>
+ <requires lib="gtk+" version="2.16"/>
+ <!-- interface-requires earcandy_window 1.0 -->
+ <!-- interface-naming-policy project-wide -->
+ <!-- interface-local-resource-path ../media -->
+ <object class="EarcandyWindow" id="earcandy_window">
+ <property name="width_request">600</property>
+ <property name="height_request">500</property>
+ <property name="title" translatable="yes">Earcandy</property>
+ <property name="icon">../media/icon.png</property>
+ <signal name="destroy" handler="on_destroy"/>
+ <child>
+ <object class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem1">
+ <property name="label">gtk-new</property>
+ <property name="visible">True</property>
+ <property name="use_action_appearance">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem2">
+ <property name="label">gtk-open</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem3">
+ <property name="label">gtk-save</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem4">
+ <property name="label">gtk-save-as</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem5">
+ <property name="label">gtk-quit</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="quit"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem2">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu2">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem6">
+ <property name="label">gtk-cut</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem7">
+ <property name="label">gtk-copy</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem8">
+ <property name="label">gtk-paste</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem9">
+ <property name="label">gtk-delete</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem2">
+ <property name="visible">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem11">
+ <property name="label">gtk-preferences</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="preferences"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_View</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem4">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkImageMenuItem" id="imagemenuitem10">
+ <property name="label">gtk-about</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">True</property>
+ <signal name="activate" handler="about"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="label" translatable="yes">Your application has been created!
+
+To start changing this user interface, run 'quickly glade', which will open Glade so you can edit the default windows and dialogs.
+
+To change the behavior and edit the python code, run 'quickly edit', which will bring up a text editor.</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="pixbuf">../media/background.png</property>
+ </object>
+ <packing>
+ <property name="padding">15</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStatusbar" id="statusbar1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="xpad">5</property>
+ <property name="ypad">5</property>
+ <property name="label" translatable="yes">Status Area</property>
+ </object>
+ <packing>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/data/ui/about_earcandy_dialog.xml b/data/ui/about_earcandy_dialog.xml
new file mode 100644
index 0000000..c6c1aa6
--- /dev/null
+++ b/data/ui/about_earcandy_dialog.xml
@@ -0,0 +1,9 @@
+<glade-catalog name="about_earcandy_dialog" domain="glade-3"
+ depends="gtk+" version="1.0">
+ <glade-widget-classes>
+ <glade-widget-class title="About Earcandy Dialog" name="AboutEarcandyDialog"
+ generic-name="AboutEarcandyDialog" parent="GtkAboutDialog"
+ icon-name="widget-gtk-about-dialog"/>
+ </glade-widget-classes>
+
+</glade-catalog>
diff --git a/data/ui/earcandy_window.xml b/data/ui/earcandy_window.xml
new file mode 100644
index 0000000..b8977d8
--- /dev/null
+++ b/data/ui/earcandy_window.xml
@@ -0,0 +1,8 @@
+<glade-catalog name="earcandy_window" domain="glade-3"
+ depends="gtk+" version="1.0">
+ <glade-widget-classes>
+ <glade-widget-class title="Earcandy Window" name="EarcandyWindow"
+ generic-name="EarcandyWindow" parent="GtkWindow"
+ icon-name="widget-gtk-window"/>
+ </glade-widget-classes>
+</glade-catalog>
diff --git a/ear_candy/Sink.py b/ear_candy/Sink.py
deleted file mode 100644
index db474f9..0000000
--- a/ear_candy/Sink.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Ear Candy - Pulseaduio sound managment tool
-# Copyright (C) 2008 Jason Taylor
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import math
-import time
-
-class Sink():
- def __init__(self, index, name, volume, client, channels=1):
-
- self.index = index
- self.name = name
- self.client = client
- self.volume = volume
- self.channels = channels
-
- self.volume_meter = 0
- self.volume_meter_last_non_zero = 0
-
- self.__previous_volume = 0
-
- def set_volume(self):
-
- current_volume = round(self.volume[1])
- step_volume = current_volume
- result = True
-
- if current_volume < self.client.volume_target:
- step_volume = current_volume + self.client.volume_step
- if not self.client.fade_volume: step_volume = self.client.volume_target
- elif current_volume > self.client.volume_target:
- step_volume = current_volume - self.client.volume_step
- if not self.client.fade_volume: step_volume = self.client.volume_target
-
- if step_volume > 100:
- step_volume = 100
- if step_volume < self.client.volume_step:
- step_volume = self.client.volume_step
-
- # we dont want to get stuck in a loop because volumes arn't exactly the same
- result = math.fabs(self.__previous_volume - current_volume) >= self.client.volume_step
-
- if result:
- for i in range(0, self.channels+1):
- self.volume[i] = step_volume
-
- #print "\nAdjust Volume", self.client.name, step_volume
-
- self.__previous_volume = step_volume
-
- return result
-
diff --git a/ear_candy/__init__.py b/ear_candy/__init__.py
deleted file mode 100644
index 4265cc3..0000000
--- a/ear_candy/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-#!/usr/bin/env python
diff --git a/ear_candy/ear_candy.py b/ear_candy/ear_candy.py
deleted file mode 100755
index 2b4e74e..0000000
--- a/ear_candy/ear_candy.py
+++ /dev/null
@@ -1,552 +0,0 @@
-#!/usr/bin/env python
-
-# Ear Candy - Pulseaduio sound managment tool
-# Copyright (C) 2008 Jason Taylor
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import gtk
-import wnck
-import gobject
-import time
-import datetime
-import re
-import os
-import copy
-import sys
-import gconf
-import time
-import shutil
-import pynotify
-from xml.dom.minidom import *
-
-from window.WindowWatcher import WindowWatcher
-from pulseaudio.PulseAudio import PulseAudio
-from Client import Client
-from Sink import Sink
-from Threads import threaded
-from TrayIcon import EarCandyStatusIcon
-from EarCandyPrefs import EarCandayPref
-from VolumeSlider import EarCandyVolumeSlider
-from EarCandyDBus import EarCandyDBusClient
-
-# Turn on gtk threading
-gtk.gdk.threads_init()
-
-def find_program_file(path):
- """Finds a program file, for example, a png included with the program.
- First looks for it in files/ under the parent directory of the parent directory
- of ear_candy.py
- Then looks for it in /usr/share/earcandy
- Returns the path of the file"""
- if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "files",path)):
- return os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "files",path)
- else:
- return os.path.join(sys.prefix, "share/earcandy", path)
-
-gtk.window_set_default_icon_from_file(find_program_file("earsLabel.png"))
-
-class EarCandy():
- def save(self):
- path = os.path.dirname(self.config_file)
- if not os.path.exists( path ):
- os.makedirs(path)
-
- # New document
- doc = Document()
- ec = doc.createElement("earcandy")
- doc.appendChild(ec)
-
- rules = doc.createElement("rules")
- ec.appendChild(rules)
- skip = copy.copy(self.ignore)
- for client in self.pa_clients.values():
- if not client.name in skip and client.category:
- # Creates user element
- el = doc.createElement("rule")
- rules.appendChild(el)
- client.to_xml(el)
- skip.append(client.name)
-
-
- doc.documentElement.setAttribute("fade_timer_speed", str(self.fade_timer_speed))
- doc.documentElement.setAttribute("mute_level", str(self.mute_level))
- doc.documentElement.setAttribute("tray_visible", str(self.tray.get_visible()))
- doc.documentElement.setAttribute("managed_output_name", str(self.managed_output_name))
- doc.documentElement.setAttribute("follow_new_outputs", str(self.follow_new_outputs))
- doc.documentElement.setAttribute("version", str(self.version))
-
- # Record outputs
- outputs = doc.createElement("outputs")
- for output in self.pa_outputs.values():
- # Creates user element
- el = doc.createElement("output")
- el.setAttribute("name", output)
- outputs.appendChild(el)
- ec.appendChild(outputs)
-
-
- fp = open(self.config_file,"w")
- doc.writexml(fp, " ", "", "\n", "UTF-8")
- fp.close()
-
- def load(self):
- settings_version = 0
- xml = None
- doc = None
-
- # Load the defaults
- f = open(self.default_config_file, "r")
- xml = f.read()
- f.close()
- default_doc = parseString(xml)
- default_version = float(default_doc.documentElement.getAttribute("version"))
-
- # Check for user settings
- if os.path.exists( self.config_file ):
- # Load XML
- try:
- f = open(self.config_file, "r")
- xml = f.read()
- f.close()
- doc = parseString(xml)
- if doc.documentElement.hasAttribute("version"):
- settings_version = float(doc.documentElement.getAttribute("version"))
- except:
- pass
-
- # Load defaults ?
- if not doc or default_version > settings_version:
- doc = default_doc
-
- # Load client rules
- for el in doc.getElementsByTagName("rule"):
- client = Client(self, "")
- client.from_xml(el)
- if client.category:
- print "Loaded client rules :", client.description
- self.pa_clients[client.name] = client
-
- self.fade_timer_speed = float(doc.documentElement.getAttribute("fade_timer_speed"))
- #self.mute_level = float(doc.documentElement.getAttribute("mute_level"))
- if doc.documentElement.hasAttribute("tray_visible"):
- self.tray.set_visible( doc.documentElement.getAttribute("tray_visible") == "True" )
- if doc.documentElement.hasAttribute("managed_output_name"):
- self.managed_output_name = doc.documentElement.getAttribute("managed_output_name")
- if doc.documentElement.hasAttribute("follow_new_outputs"):
- self.follow_new_outputs = doc.documentElement.getAttribute("follow_new_outputs") == "True"
-
- def __init__(self):
- self.version = 0.5
- self.active = True
- self.display = {"": "[ unknown ]", "phone" : "Phone (VoIP)", "video" : "Video Player", "music" : "Music Player", "event" : "Notification" }
- self.ignore = ["EsounD client (UNIX socket client)", "ear-candy", "Native client (UNIX socket client)", "PulseAudio Volume Control"]
- self.config_file = os.path.expanduser("~/.config/Ear Candy/settings.xml")
- self.default_config_file = find_program_file("settings.xml")
-
- self.pa_clients = {} # clients by name
- self.pa_clients_by_id = {} # clients by id
- self.pa_sinks = {}
- self.pa_outputs = {}
- self.pa_output_descriptions = {}
- self.client_with_focus = None # client that has a focused window
- self.last_application = None
- self.primary_client = None # Client that gets foreground sound regardless of window focus
- self.managed_output_name = ""
- self.current_source_name = ""
- self.reset_all = False
-
- self.pref = None
-
- self.fade_timer_speed = 0.1
- self.mute_level = 20
- self.follow_new_outputs = True
-
- self.priority_stack = { "" : [], "phone" : [], "video" : [], "music" : [] }
-
- pass
-
- def run(self):
- self.ecb = EarCandyDBusClient(self)
-
- if self.ecb.is_running():
- print "Ear candy already running..."
- self.ecb.show_window()
- sys.exit(0)
- else:
-
- self.tray = EarCandyStatusIcon(self)
-
- self.slider = EarCandyVolumeSlider(self)
-
- self.ecb.start_service()
- self.load()
- self.pa = PulseAudio( self.on_new_pa_client, self.on_remove_pa_client, self.on_new_pa_sink, self.on_remove_pa_sink, self.on_new_pa_output, self.on_remove_pa_output, self.on_volume_change, self.pa_volume_meter)
- self.ww = WindowWatcher(self.on_active_window_change)
-
- self.select_client_thread()
- self.apply_volume_thread()
-
- gtk.main()
- self.exit()
-
- def show_notification(self, title, body, icon):
- try:
- pynotify.init( "Ear Candy" )
- n = pynotify.Notification(title, body, icon)
- n.show ()
- except:
- print "Unable to show notification"
-
- def set_active(self, active):
- self.active = active
- if self.slider: self.slider.update_active_status()
- self.tray.set_icon()
-
-
- def open_preferances(self):
- if not self.pref:
- self.pref = EarCandayPref(self)
- self.pref.run()
-
- def close_preferances(self):
- if self.pref:
- self.pref = None
-
- def get_current_sink_volume(self):
- self.pa.get_sink_info_by_name(self.managed_output_name)
-
- @threaded
- def apply_volume_thread(self):
- while True:
- time.sleep(self.fade_timer_speed)
- if self.active:
- gobject.idle_add(self.__adjust_volumes)
-
- @threaded
- def select_client_thread(self):
- while True:
- time.sleep(0.5)
- if self.active:
- gobject.idle_add(self.__set_primary_client)
-
- def __adjust_volumes(self):
-
- # Always update based on active sinks
- for sink in self.pa_sinks.values():
- if sink.set_volume():
- # set pa volume
- self.pa.set_sink_volume(sink.index, sink.volume, sink.channels)
-
-
- def __set_window_stack(self):
- for client in self.pa_clients.values():
- # self.pref.update_client( client )
-
- # Select the primary client and order previous clients
- for key in self.priority_stack.keys():
- if client.category == key:
- # Add client to category
- if not client in self.priority_stack[key]:
- if self.client_with_focus == client:
- self.priority_stack[key].insert(0, client)
- else:
- self.priority_stack[key].append(client)
-
- # reshuffle if active
- elif self.client_with_focus == client and not self.priority_stack[key][0] == client:
- self.priority_stack[key].remove(client)
- self.priority_stack[key].insert(0, client)
-
- # If category has changed remove old entry
- elif client in self.priority_stack[key]:
- self.priority_stack[key].remove(client)
-
- def __set_primary_client(self):
-
- # Toggle primary status
- flagFound = False
- for key in self.priority_stack.keys():
- if key:
- for client in self.priority_stack[key]:
-
- if not flagFound and self.active and client.is_active():
- # Toggle old client to inactive
- if self.primary_client:
- self.primary_client.set_primary(False)
- self.primary_client = client
- client.set_primary(True)
- flagFound = True
- else:
- client.set_primary(False)
- else:
- # all clients with no category should be treated as active for volume
- for client in self.priority_stack[key]:
- client.set_primary(True)
-
-
- def on_volume_change(self, level):
- self.slider.set_volume( level )
- #print self.managed_output_name, level
-
- def on_remove_pa_output(self, index):
- # /desktop/gnome/sound/default_mixer_device
-
- del( self.pa_outputs[index] )
- del( self.pa_output_descriptions[index] )
-
- # fall back to previous value...
- for value in self.pa_outputs.values():
- self.set_last_output(value)
- return
-
- def set_last_output(self, name):
- self.managed_output_name = name
-
- # Update gconf key that governs multimedia key controls
- gconf_key = "/desktop/gnome/sound/default_mixer_device"
- prefix = "pulsemixer:"
- client = gconf.client_get_default()
- value = client.get_string(gconf_key)
- if not value == prefix + name:
- print
- print "== Change gnome default sound device =="
- client.set_string(gconf_key, prefix + name)
-
- def on_new_pa_output(self, index, output_name, output_description, startup):
- self.pa_outputs[index] = output_name
- self.pa_output_descriptions[index] = output_description
- self.save()
-
- if self.follow_new_outputs and (self.managed_output_name == "" or output_name.lower().count("usb") > 0):
- # Move all streams to the new output ;)
- if not startup:
- self.show_notification("New sound device detected", "Moving all sound to new device...", "notification-audio-volume-high")
- self.set_last_output(output_name)
- self.move_all_sinks()
-
- def move_all_sinks(self):
- if self.managed_output_name:
- for sink in self.pa_sinks.values():
- if not sink.client.output:
- self.pa.move_sink(sink.index, self.managed_output_name)
-
- def on_new_pa_sink(self, index, name, client_index, volume, sink_index, channels):
-
- if not self.pa_sinks.has_key(index):
- print
- print "== pa sink input =="
- print "sink:", index, name
- if self.pa_clients_by_id.has_key(client_index):
- client = self.pa_clients_by_id[client_index]
- print "client:", client.name
- print "client index:", client_index
- print "category:", client.category
- sink = Sink(index, name, volume, client, channels)
- client.sinks[index] = sink
- self.pa_sinks[index] = sink
-
- # insure the sink input is on the correct output..
- output = None
- if self.follow_new_outputs and self.managed_output_name:
- output = self.managed_output_name
-
- if client.output:
- output = client.output
-
- if output:
- self.pa.move_sink(sink.index, output)
-
- else:
- return
- else:
- self.pa_sinks[index].volume = volume
-
- if self.pref:
- self.pref.update_client( self.pa_clients_by_id[client_index] )
- self.on_active_window_change( self.last_application, "pa")
-
- def pa_volume_meter(self, index, level):
- if self.pa_sinks.has_key(index):
- sink = self.pa_sinks[index]
- sink.volume_meter = level
- if(level > sink.client.volume_step): sink.volume_meter_last_non_zero = time.mktime(datetime.datetime.now().timetuple())
-
- def on_remove_pa_sink(self, index):
- print
- print "== pa remove sink input =="
- print index
- if self.pa_sinks.has_key(index):
- client = self.pa_sinks[index].client
- # Delete entry in store so that volume dosnt stay low :)
- self.pa.pa_ext_stream_restore_delete( self.pa_sinks[index].name )
- del( client.sinks[index] )
- del( self.pa_sinks[index] )
- if self.pref:
- self.pref.update_client( client )
- self.on_active_window_change( self.last_application, "pa")
-
- def get_unregistered_clients(self):
- clients = []
- # client names to skip from adding to list
- skip = copy.copy(self.ignore)
- count = 0
- for client in self.pa_clients.values():
- if client.category == "" and not client.icon and not client.name in skip:
- clients.append(client)
- skip.append( client.name )
- return clients
-
- def on_new_pa_client(self, index, name, pid, proplist):
- if not pid: pid = -1
- print
- print "== pa sink client =="
- print "index:", index
- print "name:", name
- print proplist
- # Link all clients with same name into same object
-
- if not self.pa_clients.has_key(name):
- client = Client(self, name, int(pid))
- self.pa_clients[name] = client
- else:
- client = self.pa_clients[name]
- client.pid = int(pid)
- self.pa_clients_by_id[index] = client
-
- # Check windows for a match
- for application in self.ww.applications.values():
- if self.match_client_to_application(client, application, index): break
-
- def on_remove_pa_client(self, index):
- print "== pa remove client =="
- if self.pa_clients_by_id.has_key(index):
- print self.pa_clients_by_id[index].name
- client = self.pa_clients_by_id[index]
-
- # remove from by ID list
- del self.pa_clients_by_id[index]
-
- # Remove from priority array
- if client in self.priority_stack[client.category]:
- self.priority_stack[client.category].remove(client)
-
- # If not category then totaly delete the entry
- # if not client.category: del self.pa_clients[client.name]
-
- if self.pref:
- self.pref.update_client( client )
- if not client.is_active():
- self.pref.remove_client_on_timer( client )
-
- def on_active_window_change(self, application, state): #, pid, window_name, x, y, icon, fullscreen
- if application:
- # Need to run this if a client is added or removed...
- if state == "active":
- """print
- print "== active window changed =="
- print "window title:", application.window_name
- print "application:", application.name
- print "pid:", application.pid
- print "gid:", os.getpgid(application.pid)
- print "command:", application.command
- print "category:", application.category"""
-
- # set active / fullscreen flags
- if self.client_with_focus:
- self.client_with_focus.has_focus = False
-
- self.client_with_focus = None
- for client in self.pa_clients.values():
- if self.match_client_to_application(client, application, state): break
-
- self.last_application = application
-
- self.__set_window_stack()
-
- def match_client_to_application(self, client, application, index=0):
- if client.test_focus_window(application.pid, application.window_name, application.command, application.name):
-
- client.fullscreen = application.fullscreen
- if not client.icon : client.icon = application.icon
- if not client.icon_name : client.icon_name = application.icon_name
- if not client.description :client.description = application.description
- client.matched = True
- #if not state == "open":
- self.client_with_focus = client
-
- if not client.category:
- client.category = application.category
- # Balance has no practical use I can think of....
- # Im going to leave it here in case someone thinks of something
- #if client.window_position_fade:
- # client.balance = ((100 - (application.y / (gtk.gdk.screen_width() / 100))) * 2) - 100
- # print "BALANCE", client.balance
- #else:
- # client.balance = 0
- if self.pref:
- self.pref.update_client( client )
- return True
- return False
-
- def clean_client_name(self, name):
- name = name.strip()
- alsa_plugin = "ALSA plug-in ["
- if name.startswith(alsa_plugin):
- name = name[len(alsa_plugin): -1]
- return name
-
- def exit(self):
- self.set_active(False)
- # Reset all volumes
- self.reset_all_volumes()
- sys.exit(0)
-
- def reset_all_volumes(self, deleteFile=True):
- print ""
- print "Resetting all volume levels..."
- for sink in self.pa_sinks.values():
- self.pa.set_sink_volume(sink.index, [100, 100, 100], sink.channels)
-
- def set_auto_start(self, flag):
- filename = "earcandy.desktop"
- path = os.path.expanduser(os.getenv('XDG_CONFIG_HOME', '~/.config/autostart'))
- dest = os.path.join(path, filename)
- src = find_program_file(filename)
-
- if flag:
- # insure the autostart path exists
- if not os.path.exists(path):
- os.makedirs(path)
-
- if not os.path.exists( dest ):
- shutil.copyfile(src, dest)
- else:
- if os.path.exists( dest ):
- os.remove( dest )
-
- def is_auto_start(self):
- filename = "earcandy.desktop"
- dest = os.path.expanduser(os.getenv('XDG_CONFIG_HOME', '~/.config/autostart/' + filename))
- return os.path.exists(dest)
-
-if __name__ == '__main__':
-
- os.chdir(os.path.dirname(sys.argv[0]))
-
- ec = EarCandy()
- ec.set_auto_start( True )
- ec.run()
- ec.save()
-
diff --git a/ear_candy/pulseaudio/PulseAudio.py b/ear_candy/pulseaudio/PulseAudio.py
deleted file mode 100644
index 2f7e565..0000000
--- a/ear_candy/pulseaudio/PulseAudio.py
+++ /dev/null
@@ -1,337 +0,0 @@
-# Ear Candy - Pulseaduio sound managment tool
-# Copyright (C) 2008 Jason Taylor
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from lib_pulseaudio import *
-import gobject
-import sys
-import os
-import ctypes
-
-PA_VOLUME_CONVERSION_FACTOR = 655.36
-
-# A null method that can be given to pulse methods
-def null_cb(a=None, b=None, c=None, d=None):
- #print "NULL CB"
- return
-
-class PulseAudio():
- def __init__(self, new_client_cb, remove_client_cb, new_sink_cb, remove_sink_cb, new_output_cb, remove_pa_output, volume_change_cb, volume_meter_cb):
-
- self.sinks = {}
- self.monitor_sinks = []
- self.module_stream_restore_argument = ""
-
- self.new_client_cb = new_client_cb
- self.new_sink_cb = new_sink_cb
- self.remove_sink_cb = remove_sink_cb
- self.remove_client_cb = remove_client_cb
- self.new_output_cb = new_output_cb
- self.volume_change_cb = volume_change_cb
- self.remove_pa_output = remove_pa_output
- self.volume_meter_cb = volume_meter_cb
-
- self.pa_mainloop = pa_threaded_mainloop_new();
- self.pa_mainloop_api = pa_threaded_mainloop_get_api(self.pa_mainloop);
-
- self._context = pa_context_new(self.pa_mainloop_api, "ear-candy");
- self._context_notify_cb = pa_context_notify_cb_t(self.context_notify_cb)
- pa_context_set_state_callback(self._context, self._context_notify_cb, None);
- pa_context_connect(self._context, None, 0, None);
-
- pa_threaded_mainloop_start(self.pa_mainloop);
-
- # pulseaudio connection status
- def context_notify_cb(self, context, userdata):
-
- try:
- ctc = pa_context_get_state(context)
- if ctc == PA_CONTEXT_READY:
- print
- print "Pulseaudio connection ready..."
-
- self._null_cb = pa_context_success_cb_t(null_cb)
- self._pa_context_success_cb = pa_context_success_cb_t(self.pa_context_success_cb)
- self._pa_stream_request_cb = pa_stream_request_cb_t(self.pa_stream_request_cb)
- self._pa_stream_notify_cb = pa_stream_notify_cb_t(self.pa_stream_request_cb)
- self._pa_sink_info_cb = pa_sink_info_cb_t(self.pa_sink_info_cb)
- self._pa_context_subscribe_cb = pa_context_subscribe_cb_t(self.pa_context_subscribe_cb)
- self._pa_source_info_cb = pa_source_info_cb_t(self.pa_source_info_cb)
- self._pa_source_output_info_cb = pa_source_output_info_cb_t(self.pa_source_output_info_cb)
- self._pa_sink_input_info_list_cb = pa_sink_input_info_cb_t(self.pa_sink_input_info_cb)
- self._pa_client_info_list_cb = pa_client_info_cb_t(self.pa_client_info_cb)
- self._pa_module_info_cb = pa_module_info_cb_t(self.pa_module_info_cb)
- self._pa_context_index_cb = pa_context_index_cb_t(self.pa_context_index_cb)
-
- o = pa_context_get_module_info_list(self._context, self._pa_module_info_cb, True)
- pa_operation_unref(o)
-
- o = pa_context_get_source_info_list(self._context, self._pa_source_info_cb, True)
- pa_operation_unref(o)
-
- o = pa_context_get_client_info_list(self._context, self._pa_client_info_list_cb, None)
- pa_operation_unref(o)
-
- o = pa_context_get_source_output_info_list(self._context, self._pa_source_output_info_cb, None)
- pa_operation_unref(o)
-
- o = pa_context_get_sink_info_list(self._context, self._pa_sink_info_cb, None)
- pa_operation_unref(o)
-
- o = pa_context_get_sink_input_info_list(self._context, self._pa_sink_input_info_list_cb, True)
- pa_operation_unref(o)
-
- pa_context_set_subscribe_callback(self._context, self._pa_context_subscribe_cb, None);
- o = pa_context_subscribe(self._context, (pa_subscription_mask_t)
- (PA_SUBSCRIPTION_MASK_SINK|
- PA_SUBSCRIPTION_MASK_SOURCE|
- PA_SUBSCRIPTION_MASK_SINK_INPUT|
- PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT|
- PA_SUBSCRIPTION_MASK_CLIENT|
- PA_SUBSCRIPTION_MASK_SERVER), self._null_cb, None)
-
- pa_operation_unref(o)
-
- if ctc == PA_CONTEXT_FAILED :
- self.__print("Connection failed")
- pa_threaded_mainloop_signal(self.pa_mainloop, 0)
- sys.exit(1)
-
- if ctc == PA_CONTEXT_TERMINATED:
- self.__print("Connection terminated")
- pa_threaded_mainloop_signal(self.pa_mainloop, 0)
- sys.exit(1)
-
- except Exception, text:
- self.__print("ERROR context_notify_cb %s" % text)
-
-
- def pa_context_index_cb(self, context, index, user_data):
- # Do nothing....
- return
-
- def load_module_stream_restore(self):
- print "Reloading module-stream-restore "
- pa_context_load_module(self._context, "module-stream-restore", self.module_stream_restore_argument, self._pa_context_index_cb, None)
-
-
- def pa_module_info_cb(self, context, pa_module_info, eol, user_data):
- if user_data and pa_module_info:
-
- """if pa_module_info.contents.name == "module-stream-restore":
- print
- print "Found 'module-stream-restore'... unloading.."
- self.module_stream_restore_argument = pa_module_info.contents.argument
- pa_context_unload_module(context, pa_module_info.contents.index, self._null_cb, None)"""
- return
-
- def pa_source_info_cb(self, context, struct, eol, user_data):
- if eol: return
-
- if struct:
-
- if struct.contents.monitor_of_sink_name:
- print
- print "== new output device found =="
- print struct.contents.name
- print struct.contents.description
- """print struct.contents.monitor_of_sink_name
- print struct.contents.driver
- print struct.contents.sample_spec
- print struct.contents.channel_map
- print struct.contents.owner_module
- print struct.contents.volume
- print struct.contents.mute
- print struct.contents.monitor_of_sink
- print struct.contents.monitor_of_sink_name
- print struct.contents.flags
- print struct.contents.index"""
-
- gobject.idle_add(self.new_output_cb, struct.contents.index, struct.contents.monitor_of_sink_name, struct.contents.description, user_data)
- volume = int(pa_cvolume_avg(struct.contents.volume) / PA_VOLUME_CONVERSION_FACTOR)
- gobject.idle_add(self.volume_change_cb, volume)
-
- def pa_stream_request_cb(self, stream, length, index):
-
- # This isnt quite right... maybe not a float.. ?
-
- #null_ptr = ctypes.c_void_p()
- data = POINTER(c_float)()
- pa_stream_peek(stream, data, ctypes.c_ulong(length))
- v = data[length / 4 -1] * 100
- if (v < 0):
- v = 0
- if (v > 100):
- v = 100
- pa_stream_drop(stream)
-
- self.volume_meter_cb(index, v)
-
- def pa_create_monitor_stream_for_sink_input(self, index, monitor_index, name):
-
- if not index in self.monitor_sinks:
- self.monitor_sinks.append(index)
- # Create new stream
- ss = pa_sample_spec()
- ss.channels = 1
- ss.format = 5
- ss.rate = 25
- pa_stream = pa_stream_new(self._context, "Peak detect - " + name, ss, None)
-
- pa_stream_set_monitor_stream(pa_stream, index);
- pa_stream_set_read_callback(pa_stream, self._pa_stream_request_cb, index);
- pa_stream_set_suspended_callback(pa_stream, self._pa_stream_notify_cb, None);
-
- attr = pa_buffer_attr()
- attr.fragsize = 4
- attr.maxlength = 10
- attr.tlength = 0
- attr.prebuf = 0
- attr.minreq = 0
-
- pa_stream_connect_record(pa_stream, str(monitor_index), attr, 10752)
-
- def pa_context_success_cb(self, context, c_int, user_data):
- return
-
- def pa_source_output_info_cb(self, context, struct, c_int, user_data):
- return
-
- def pa_context_subscribe_cb(self, context, event_type, index, user_data):
-
- try:
- et = event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK
-
- if et == PA_SUBSCRIPTION_EVENT_CLIENT:
-
- if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
- gobject.idle_add(self.remove_client_cb, int(index))
- else:
- o = pa_context_get_client_info(self._context, index, self._pa_client_info_list_cb, None)
- pa_operation_unref(o)
-
- if et == PA_SUBSCRIPTION_EVENT_SINK_INPUT:
- if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
- gobject.idle_add(self.remove_sink_cb, int(index))
- self.monitor_sinks.remove(index)
- else:
- o = pa_context_get_sink_input_info(self._context, int(index), self._pa_sink_input_info_list_cb, True)
- pa_operation_unref(o)
-
- if et == PA_SUBSCRIPTION_EVENT_SOURCE:
- if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
- # Remove output source
- self.remove_pa_output( int(index) )
- else:
- o = pa_context_get_source_info_by_index(self._context, int(index), self._pa_source_info_cb, False)
- pa_operation_unref(o)
-
- except Exception, text:
- self.__print("pa :: ERROR pa_context_subscribe_cb %s" % text)
-
- def pa_client_info_cb(self, context, struct, c_int, user_data):
- try:
- if struct :
-
- self.__print("CLIENT")
- self.__print( pa_proplist_to_string(struct.contents.proplist))
-
- # Get the client pid so we can match to the x11 window application pid
- pid = pa_proplist_gets(struct.contents.proplist, "application.process.id")
- #binary = pa_proplist_gets(struct.contents.proplist, "application.process.binary")
-
- gobject.idle_add(self.new_client_cb, struct.contents.index, struct.contents.name, pid, pa_proplist_to_string(struct.contents.proplist))
-
- except Exception, text:
- self.__print( "pa :: ERROR pa_client_info_cb %s" % text)
-
- def pa_sink_input_info_cb(self, context, struct, index, user_data):
- if struct and user_data:
-
- # TODO: Only do this if app dosnt release pulse streams correctly
- if float(struct.contents.sink) in self.sinks:
- self.pa_create_monitor_stream_for_sink_input(int(struct.contents.index), self.sinks[float(struct.contents.sink)], struct.contents.name)
-
- # here we go...
- self.__print( "SINK INPUT INFO")
- self.__print( pa_proplist_to_string(struct.contents.proplist))
-
- # Get volume level
- volume = []
- volume.append( int(pa_cvolume_avg(struct.contents.volume) / PA_VOLUME_CONVERSION_FACTOR) )
- for i in range(0, struct.contents.volume.channels):
- volume.append(int(struct.contents.volume.values[i]) / PA_VOLUME_CONVERSION_FACTOR)
-
- gobject.idle_add( self.new_sink_cb, int(struct.contents.index), struct.contents.name, int(struct.contents.client), volume, struct.contents.sink, struct.contents.channel_map.channels)
-
- # Move a playing stream to a differnt output sink
- def move_sink(self, sink_index, output_name):
- self.__print("move_sink")
- pa_context_move_sink_input_by_name(self._context, sink_index, output_name, self._pa_context_success_cb, None)
-
- def set_sink_volume_by_name(self, sink_name, volume):
- self.__print("set_sink_volume_by_name")
- if volume < 0: volume = 0
- vol = pa_cvolume()
- vol.channels = 2 #len(cvolume) - 1
- v = pa_volume_t * 32
- vol.values = v (int(volume * PA_VOLUME_CONVERSION_FACTOR), int(volume * PA_VOLUME_CONVERSION_FACTOR))
-
- o = pa_context_set_sink_volume_by_name(self._context, sink_name, vol, self._null_cb, None)
- pa_operation_unref(o)
-
- def set_sink_volume(self, index, cvolume, number_of_channels):
- self.__print("set_sink_volume")
- vol = pa_cvolume()
- vol.channels = number_of_channels #len(cvolume) - 1
- v = pa_volume_t * 32
-
- vol.values = v()
- for i in range(0, number_of_channels):
- if len(cvolume) > i:
- vol.values[i] = int(cvolume[i+1] * PA_VOLUME_CONVERSION_FACTOR)
- else:
- vol.values[i] = int(cvolume[1] * PA_VOLUME_CONVERSION_FACTOR)
-
- # Note setting volume causes a trigger of sink_input_info which will gives us back new volume!
- o = pa_context_set_sink_input_volume(self._context, index, vol, self._null_cb, None) # NOTE: dont pass in any thing here causes a seg fault
- pa_operation_unref(o)
-
- def get_sink_info_by_name(self, sink_name):
- self.__print("get_sink_info_by_name")
- o = pa_context_get_sink_info_by_name(self._context, sink_name, self._pa_sink_info_cb, False)
- pa_operation_unref(o)
-
- def pa_sink_info_cb(self, context, struct, index, data):
- if struct:
- # Get volume level
-
- # Update sink to monitor links
- self.sinks[ float(struct.contents.index) ] = struct.contents.monitor_source
- self.__print("pa_sink_info_cb")
- gobject.idle_add(self.volume_change_cb, int(pa_cvolume_avg(struct.contents.volume) / PA_VOLUME_CONVERSION_FACTOR))
-
- def pa_ext_stream_restore_delete( self, stream ):
- pa_ext_stream_restore_delete(self._context, stream, self._pa_context_success_cb, None)
-
-
-
- def __print(self, text):
- #print text
- return
-
-if __name__ == '__main__':
- c = PulseAudio()
-
diff --git a/ear_candy/window/WindowWatcher.py b/ear_candy/window/WindowWatcher.py
deleted file mode 100644
index 51c1a78..0000000
--- a/ear_candy/window/WindowWatcher.py
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env python
-# Ear Candy - Pulseaduio sound managment tool
-# Copyright (C) 2008 Jason Taylor
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-# based on original code by by RYX (aka Rico Pfaus)
-# http://thegraveyard.org/files/kapager-0.0.3.py
-
-import wnck
-import gobject
-import os
-import gtk
-from DesktopFiles import DesktopFiles
-
-class Application():
- def __init__(self, pid, command, name, desktop_files, icon):
- self.command = os.path.basename(command.lower().strip())
- self.name = name.lower().strip()
- self.category = ""
- self.icon = icon
- self.icon_name = ""
- self.description = name
-
- # current application window info
- self.pid = pid
- self.window_name = ""
- self.x = 0
- self.y = 0
- self.fullscreen = False
-
- self.__match_desktop(desktop_files)
- return
-
- def __match_desktop(self, desktop_files):
-
- #print "checking desktop files for match....", self.name
- # Try looking up the application list
- for a in desktop_files:
- ex = a.get_exec_array()[0].lower()
-
- # Check for pulse audio settings first...
- if a.get("X-PulseAudio-Properties"):
- self.category = a.get("X-PulseAudio-Properties")
- break
-
- if a.get("X-GNOME-Bugzilla-Product") == self.name or ex == self.name or ex == self.command or a.get("Name").lower() == self.name or a.get("Name").lower() == self.command or ex + ".real" == self.command :
- self.icon_name = a.get("Icon")
- self.description = a.get("Name")
- self.category = self.__desktop_categories_to_category( a.get("Categories") )
- #print "category : " + (self.category or "<unknown>")
- break
-
- if self.command == "skype.real":
- self.category = "phone"
- #print "category : " + self.category
-
-
- def __desktop_categories_to_category(self, categories):
- if categories:
- for category in categories:
- if "Telephony" in categories or "InstantMessaging" in categories:
- return "phone"
- if "Music" in categories:
- return "music"
- if "Video" in categories:
- return "video"
- if "AudioVideo" in categories:
- return "music"
-
- return ""
-
-class WindowWatcher():
-
- def __init__(self, callback = None, existing=False):
- self.screen = wnck.screen_get_default()
- self.screen.connect("active_window_changed", self.active_window_changed)
- self.screen.connect("window_opened", self.window_opened)
- self.callback = callback
- self.current_window = None
- self.applications = {}
- self.desktop_files = DesktopFiles().read_all()
-
- for win in self.screen.get_windows():
- self.update("exists", win)
-
- def send_window_key_press_from_pid(self, pid, key):
- for win in self.screen.get_windows():
- app = win.get_application()
- app_name = app.get_name()
- if pid == win.get_pid():
-
- break;
-
-
- def get_command(self, pid):
- command = str(pid)
- try:
- # Try and get command from PID
- f = open("/proc/%s/cmdline" % pid, "r")
- command = f.readline()[:-1]
- f.close()
- except:
- pass
- return command
-
- def window_opened(self, screen, win):
- if win and win.get_window_type() != wnck.WINDOW_DOCK: # ignore docks
- #print
- #print "window opened"
- app = win.get_application ()
- app_name = app.get_name()
- pid = win.get_pid()
- command = self.get_command(pid)
- category = ""
-
- if not self.applications.has_key(command):
- a = Application(pid, command, app_name, self.desktop_files, win.get_icon ())
- self.applications[command] = a
-
- self.update("open", win)
- win.connect("geometry-changed", self.geometry_changed)
-
- def active_window_changed(self, screen, old_window):
- win = screen.get_active_window ()
- self.update("active", win)
-
- def update(self, state, win):
- if win and win.get_window_type() != wnck.WINDOW_DOCK: # ignore docks
-
- #try:
- app = win.get_application ()
- app_name = app.get_name()
- pid = win.get_pid()
-
- command = self.get_command(pid)
-
- application = None
- application = self.applications[command]
- application.pid = pid
-
- geom = win.get_geometry()
- x = float(geom[1])
- y = float(geom[0])
- w = float(geom[2])
- h = float(geom[3])
-
- if win.get_name():
- application.window_name = win.get_name()
-
- application.x = x+h/2
- application.y = y+w/2
- application.fullscreen = win.is_fullscreen () or win.is_maximized ()
- application.icon = win.get_icon ()
-
-
- if self.callback:
- gobject.idle_add(self.callback, application, state)
- #except:
- # print "error reading window", win
-
- def geometry_changed(self, win):
- self.update("geo", win)
-
-
-if __name__ == '__main__':
- ww = WindowWatcher()
-
diff --git a/earcandy.desktop.in b/earcandy.desktop.in
new file mode 100644
index 0000000..1aa0283
--- /dev/null
+++ b/earcandy.desktop.in
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Name=Earcandy
+Comment=Earcandy application
+Categories=GNOME;AudioVideo;
+Exec=earcandy
+Icon=/usr/share/earcandy/media/icon.png
+Terminal=false
+Type=Application
diff --git a/earcandy/AboutEarcandyDialog.py b/earcandy/AboutEarcandyDialog.py
new file mode 100644
index 0000000..0607548
--- /dev/null
+++ b/earcandy/AboutEarcandyDialog.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+### BEGIN LICENSE
+# This file is in the public domain
+### END LICENSE
+
+import sys
+import os
+import gtk
+
+from earcandy.earcandyconfig import getdatapath
+
+class AboutEarcandyDialog(gtk.AboutDialog):
+ __gtype_name__ = "AboutEarcandyDialog"
+
+ def __init__(self):
+ """__init__ - This function is typically not called directly.
+ Creation of a AboutEarcandyDialog requires redeading the associated ui
+ file and parsing the ui definition extrenally,
+ and then calling AboutEarcandyDialog.finish_initializing().
+
+ Use the convenience function NewAboutEarcandyDialog to create
+ NewAboutEarcandyDialog objects.
+
+ """
+ pass
+
+ def finish_initializing(self, builder):
+ """finish_initalizing should be called after parsing the ui definition
+ and creating a AboutEarcandyDialog object with it in order to finish
+ initializing the start of the new AboutEarcandyDialog instance.
+
+ """
+ #get a reference to the builder and set up the signals
+ self.builder = builder
+ self.builder.connect_signals(self)
+
+ #code for other initialization actions should be added here
+
+def NewAboutEarcandyDialog():
+ """NewAboutEarcandyDialog - returns a fully instantiated
+ AboutEarcandyDialog object. Use this function rather than
+ creating a AboutEarcandyDialog instance directly.
+
+ """
+
+ #look for the ui file that describes the ui
+ ui_filename = os.path.join(getdatapath(), 'ui', 'AboutEarcandyDialog.ui')
+ if not os.path.exists(ui_filename):
+ ui_filename = None
+
+ builder = gtk.Builder()
+ builder.add_from_file(ui_filename)
+ dialog = builder.get_object("about_earcandy_dialog")
+ dialog.finish_initializing(builder)
+ return dialog
+
+if __name__ == "__main__":
+ dialog = NewAboutEarcandyDialog()
+ dialog.show()
+ gtk.main()
+
diff --git a/earcandy/Application.py b/earcandy/Application.py
new file mode 100644
index 0000000..c1990f8
--- /dev/null
+++ b/earcandy/Application.py
@@ -0,0 +1,59 @@
+import gtk
+from util.DesktopFiles import DesktopFiles
+
+class Application():
+ def __init__(self, pid, command, name, icon):
+ self.command = os.path.basename(command.lower().strip())
+ self.name = name.lower().strip()
+ self.category = ""
+ self.icon = icon
+ self.icon_name = ""
+ self.description = name
+
+ # current application window info
+ self.pid = pid
+ self.window_name = ""
+ self.x = 0
+ self.y = 0
+ self.fullscreen = False
+
+ self.clients = {}
+ return
+
+ def __match_desktop(self, desktop_files):
+
+ #print "checking desktop files for match....", self.name
+ # Try looking up the application list
+ for a in desktop_files:
+ ex = a.get_exec_array()[0].lower()
+
+ # Check for pulse audio settings first...
+ if a.get("X-PulseAudio-Properties"):
+ self.category = a.get("X-PulseAudio-Properties")
+ break
+
+ if a.get("X-GNOME-Bugzilla-Product") == self.name or ex == self.name or ex == self.command or a.get("Name").lower() == self.name or a.get("Name").lower() == self.command or ex + ".real" == self.command :
+ self.icon_name = a.get("Icon")
+ self.description = a.get("Name")
+ self.category = self.__desktop_categories_to_category( a.get("Categories") )
+ #print "category : " + (self.category or "<unknown>")
+ break
+
+ if self.command == "skype.real":
+ self.category = "phone"
+ #print "category : " + self.category
+
+
+ def __desktop_categories_to_category(self, categories):
+ if categories:
+ for category in categories:
+ if "Telephony" in categories or "InstantMessaging" in categories:
+ return "phone"
+ if "Music" in categories:
+ return "music"
+ if "Video" in categories:
+ return "video"
+ if "AudioVideo" in categories:
+ return "music"
+
+ return ""
diff --git a/ear_candy/Client.py b/earcandy/Client.py
similarity index 62%
rename from ear_candy/Client.py
rename to earcandy/Client.py
index 1a3a581..e08794a 100644
--- a/ear_candy/Client.py
+++ b/earcandy/Client.py
@@ -24,41 +24,56 @@ class Client():
self.core = core
self.name = name
- self.description = self.core.clean_client_name(name)
- self.volume_default = 100
- self.volume_mute = -1
+ self.description = self.clean_client_name(name)
+ self.pid = pid
+ self.role = "" # music | video | phone
+ self.icon = None
+
+ self.volume_max = 100
+ self.volume_min = 0
+ self.volume_target = 100
+ self.volume_step = 2
+
self.rule_re_window_title = re.compile("")
self.rule_re_command = re.compile("")
self.rule_re_application = re.compile("")
- self.category = "" # music | video | phone
+
self.apply_volume_meter_hack = True
self.fade_volume = True
- self.output = ""
-
- self.pid = pid
- self.volume_step = 2
- self.has_focus = False
- self.fullscreen = False
- self.icon = None
- self.icon_name = ""
- self.iter = None
- self.balance = 0
+ self.prefer_sink = ""
- self.volume_target = self.volume_default
+ self.dbus_name = ""
+ self.__pause_status = 0
+ self.application = None
self.sinks = {}
-
self.generate_rules()
- # TODO: remove these
- self.window_position_fade = False
- self.matched = False
+ self.__status = False
+
+ self.icon_name = None
+
+ self.iter = None
+ self.gtk = None
+
+ def clean_client_name(self, name):
+ name = name.strip()
+ alsa_plugin = "ALSA plug-in ["
+ if name.startswith(alsa_plugin):
+ self.apply_volume_meter_hack = True
+ name = name[len(alsa_plugin): -1]
+ return name
+
+ def get_sink(self):
+ for sink_input in self.sinks.values():
+ return sink_input.sink
+ return -1
def get_volume(self):
v = 0
count = 0
for sink in self.sinks.values():
- v = v + sink.volume[0]
+ v = v + sink.volume_meter_level
count = count + 1
if count > 0: return v / count
return v
@@ -73,31 +88,19 @@ class Client():
return v
def is_active(self):
+ for sink in self.sinks.values():
+ if sink.is_active():
+ return True
+ return False
- # no sinks then not active
- if len(self.sinks.values()) == 0: return False
-
- timestamp = time.mktime(datetime.datetime.now().timetuple())
-
- # check meter levels on others
- if self.apply_volume_meter_hack:
- for sink in self.sinks.values():
-
- if sink.volume_meter > 0: return True
-
- # HACK: Check how long its been inactive... and if more than a second count as expired
- if timestamp - sink.volume_meter_last_non_zero < 1:
- return True
- return False
- return True
def has_rule(self):
return self.rule_re_command.pattern or self.rule_re_window_title.pattern or self.rule_re_application.pattern
def generate_rules(self, app=None):
if not app:
- app = self.core.clean_client_name(self.name).lower()
+ app = self.clean_client_name(self.name).lower()
else:
app = app.lower()
@@ -110,7 +113,6 @@ class Client():
# It will fail for things like gstreamer preview and nautilus that have no process link
# until thats fixed at a lower level we have our trusty regular expressions
if pid and self.pid and pid == self.pid:
- #print "Match PID", pid, self.pid
return True
# Fall back rules if pid matching fails
@@ -133,52 +135,90 @@ class Client():
return False
def __fade_in(self):
- self.volume_target = self.volume_default
+ self.volume_target = self.volume_max
def __fade_out(self):
- if self.volume_mute == -1:
+ if self.volume_min == -1:
self.volume_target = self.core.mute_level
else:
- self.volume_target = self.volume_mute
+ self.volume_target = self.volume_min
def __fade_mute(self):
- self.volume_target = self.volume_step # if we goto 0 than our volume meter will never register a value
+
+ if self.volume_min == -1:
+ self.volume_target = self.core.mute_level
+ else:
+ self.volume_target = self.volume_min
+
+ # if we goto 0 than our volume meter will never register a value
+ if self.volume_target < self.volume_step:
+ self.volume_target = self.volume_step
- def set_primary(self, value):
- if value or self.category == "default":
+ def set_status(self, value):
+ if value or self.role == "default":
self.__fade_in()
+ #if self.__pause_status > 0:
+ # for plugin in self.plugins:
+ # if plugin.enabled and not plugin.is_playing():
+ # if plugin.set_pause(False):
+ # self.__pause_status = 0
else:
self.__fade_mute()
+ #if self.is_active() and self.__pause_status == 0:
+ # self.__pause_status = 1
+ self.__status = value
+
+ def get_status(self):
+ for sink in self.sinks.values():
+ if sink.get_status():
+ return True
+ return False
+
+
+ # called by sink, check if all sinks are at correct volume
+ def check_volume(self):
+ result = True
+ for sink in self.sinks.values():
+ result = result and sink.volume_check
+
+ if result and self.__pause_status == 1:
+ #for plugin in self.plugins:
+ # if plugin.enabled and plugin.is_playing():
+ # plugin.set_pause(True)
+ self.__pause_status = 2
def to_xml(self, el):
# Set attributes to user element
el.setAttribute("name", self.name)
el.setAttribute("description", str(self.description))
- el.setAttribute("volume_default", str(self.volume_default))
- el.setAttribute("volume_mute", str(self.volume_mute))
+ el.setAttribute("volume_max", str(self.volume_max))
+ el.setAttribute("volume_min", str(self.volume_min))
el.setAttribute("rule_re_window_title",self.rule_re_window_title.pattern)
el.setAttribute("rule_re_command",self.rule_re_command.pattern)
el.setAttribute("rule_re_application",self.rule_re_application.pattern)
- el.setAttribute("category",self.category)
- el.setAttribute("window_position_fade", str(self.window_position_fade))
+ el.setAttribute("role",self.role)
el.setAttribute("icon_name", str(self.icon_name))
el.setAttribute("apply_volume_meter_hack", str(self.apply_volume_meter_hack))
el.setAttribute("fade_volume", str(self.fade_volume))
+ el.setAttribute("prefer_sink", self.prefer_sink)
- if self.category == "default":
- self.category = "event"
+ if self.role == "default":
+ self.role = "event"
def from_xml(self, el):
if(el.hasAttribute("name")) : self.name = el.getAttribute("name")
if(el.hasAttribute("description")) : self.description = el.getAttribute("description")
if(el.hasAttribute("icon_name")) : self.icon_name = el.getAttribute("icon_name")
- if(el.hasAttribute("volume_default")) : self.volume_default = int(el.getAttribute("volume_default"))
- #self.volume_mute = int(el.getAttribute("volume_mute"))
+ if(el.hasAttribute("volume_max")) : self.volume_max = int(el.getAttribute("volume_max"))
+ if(el.hasAttribute("volume_min")) : self.volume_min = int(el.getAttribute("volume_min"))
if(el.hasAttribute("rule_re_window_title")) : self.rule_re_window_title = re.compile(el.getAttribute("rule_re_window_title"), re.IGNORECASE)
if(el.hasAttribute("rule_re_command")) : self.rule_re_command = re.compile(el.getAttribute("rule_re_command"), re.IGNORECASE)
if(el.hasAttribute("rule_re_application")) : self.rule_re_application = re.compile(el.getAttribute("rule_re_application"), re.IGNORECASE)
- if(el.hasAttribute("category")) : self.category = el.getAttribute("category")
+ if(el.hasAttribute("role")) : self.role = el.getAttribute("role")
if(el.hasAttribute("window_position_fade")) : self.window_position_fade = el.getAttribute("window_position_fade") == "True"
if(el.hasAttribute("apply_volume_meter_hack")): self.apply_volume_meter_hack = el.getAttribute("apply_volume_meter_hack") == "True"
if(el.hasAttribute("fade_volume")): self.fade_volume = el.getAttribute("fade_volume") == "True"
+ if(el.hasAttribute("prefer_sink")): self.prefer_sink = el.getAttribute("prefer_sink")
+
+
diff --git a/earcandy/EarCandy.py b/earcandy/EarCandy.py
new file mode 100644
index 0000000..58c9144
--- /dev/null
+++ b/earcandy/EarCandy.py
@@ -0,0 +1,433 @@
+
+import os
+import copy
+import sys
+import time
+import logging
+import shutil
+from xml.dom.minidom import *
+
+import gconf
+import pynotify
+import gtk
+import gobject
+import pynotify
+
+from util.DesktopFiles import DesktopFiles
+from util.Threads import threaded
+
+from Client import Client
+from Sink import Sink
+from windows.Watcher import Watcher
+from PulseAudio import PulseAudio
+
+from ui.TrayIcon import EarCandyStatusIcon
+from ui.EarCandyPrefs import EarCandayPrefs
+
+from EarCandyDBus import EarCandyDBusClient
+
+from earcandyconfig import getdatapath
+
+log = logging.getLogger('EarCandy')
+log.setLevel(logging.DEBUG)
+
+
+class EarCandy():
+
+ def __init__(self):
+
+ self.version = 0.7
+ self.active = True
+
+ self.display = {
+ "": "[ unknown ]",
+ "phone" : "Phone (VoIP)",
+ "video" : "Video Player",
+ "music" : "Music Player",
+ "event" : "Notification",
+ "game" : "Game" }
+
+ self.ignore = ["EsounD client (UNIX socket client)",
+ "ear-candy",
+ "Native client (UNIX socket client)",
+ "PulseAudio Volume Control"]
+
+ self.config_file = os.path.expanduser("~/.config/Ear Candy/settings.xml")
+ self.default_config_file = os.path.join(getdatapath(), 'defaults', 'settings.xml')
+
+ self.pa = PulseAudio(self)
+ self.window_watcher = Watcher()
+ self.window_watcher.callback = self.on_active_window_change
+
+ self.prefer_sink = None
+
+ self.pref = None
+
+ pynotify.init('earcandy')
+
+ def notify(self, name, text):
+ return
+ #n = pynotify.Notification(name, text, None)
+ #n.show()
+
+ def init(self):
+ self.is_mute = False
+ self.mute_phone = False
+
+ self.fade_timer_speed = 0.01
+ self.mute_level = 20
+ self.follow_new_outputs = True
+
+ self.priority_stack = { "" : [], "phone" : [], "video" : [], "music" : [] , "game" : [] }
+
+ self.apply_volume_thread_running = False
+ self.select_client_thread_running = False
+
+ def run(self):
+
+ ecb = EarCandyDBusClient(self)
+ if ecb.is_running():
+ print "Ear candy already running..."
+ ecb.show_window()
+ sys.exit(0)
+
+ ecb.start_service()
+
+ self.init()
+ self.load()
+ self.pa.connect()
+
+ self.apply_volume_thread()
+ self.select_client_thread()
+
+ self.status_icon = EarCandyStatusIcon(self)
+ self.start()
+
+ self.window_watcher.check_all()
+
+
+ #self.open_preferances()
+
+ def exit(self):
+ self.stop()
+ self.pa.disconnect()
+ gtk.main_quit()
+
+
+ def stop(self):
+ self.active = False
+ while self.apply_volume_thread_running or self.select_client_thread_running:
+ time.sleep(0.1)
+
+ def start(self):
+ self.active = True
+ while not self.apply_volume_thread_running or not self.select_client_thread_running:
+ time.sleep(0.1)
+
+ def reset_all(self):
+ # tricky we want to delete all client settings and reload our default xml file
+ self.stop()
+ self.pa.disconnect()
+ self.init()
+ self.load(False)
+ self.pa.connect()
+ self.start()
+ return
+
+ def reset(self):
+ self.stop()
+ self.pa.disconnect()
+ self.init()
+ self.load()
+ self.pa.connect()
+ self.start()
+ return
+
+ def save(self):
+ path = os.path.dirname(self.config_file)
+ if not os.path.exists( path ):
+ os.makedirs(path)
+
+ # New document
+ doc = Document()
+ ec = doc.createElement("earcandy")
+ doc.appendChild(ec)
+
+ rules = doc.createElement("rules")
+ ec.appendChild(rules)
+ skip = copy.copy(self.ignore)
+ for client in self.pa.clients.values():
+ if not client.name in skip and client.role:
+ # Creates user element
+ el = doc.createElement("rule")
+ rules.appendChild(el)
+ client.to_xml(el)
+ skip.append(client.name)
+
+ doc.documentElement.setAttribute("fade_timer_speed", str(self.fade_timer_speed))
+ doc.documentElement.setAttribute("mute_level", str(self.mute_level))
+ doc.documentElement.setAttribute("tray_visible", str(self.status_icon.get_visible()))
+ doc.documentElement.setAttribute("prefer_sink", str(self.prefer_sink))
+ doc.documentElement.setAttribute("follow_new_outputs", str(self.follow_new_outputs))
+ doc.documentElement.setAttribute("version", str(self.version))
+ doc.documentElement.setAttribute("mute_phone", str(self.mute_phone))
+
+ # Record outputs
+ outputs = doc.createElement("outputs")
+ for sink in self.pa.sinks.values():
+ # Creates user element
+ el = doc.createElement("output")
+ el.setAttribute("name", sink.name)
+ el.setAttribute("priority", str(sink.priority))
+ outputs.appendChild(el)
+ ec.appendChild(outputs)
+
+ # Plugin status
+ """
+ plugins = doc.createElement("plugins")
+ for plugin in self.plugin_manager.plugins:
+ # Creates user element
+ el = doc.createElement("plugin")
+ el.setAttribute("name", plugin.get_plugin_name())
+ el.setAttribute("enabled", str(plugin.enabled))
+ plugins.appendChild(el)
+ ec.appendChild(plugins)"""
+
+ fp = open(self.config_file,"w")
+ doc.writexml(fp, " ", "", "\n", "UTF-8")
+ fp.close()
+
+ def load(self, user_settings=True):
+
+ settings_version = 0
+ xml = None
+ doc = None
+
+ # Load the defaults
+ f = open(self.default_config_file, "r")
+ xml = f.read()
+ f.close()
+ default_doc = parseString(xml)
+ default_version = float(default_doc.documentElement.getAttribute("version"))
+
+ # Check for user settings
+ if user_settings and os.path.exists( self.config_file ):
+ # Load XML
+ try:
+ f = open(self.config_file, "r")
+ xml = f.read()
+ f.close()
+ doc = parseString(xml)
+ if doc.documentElement.hasAttribute("version"):
+ settings_version = float(doc.documentElement.getAttribute("version"))
+ except:
+ pass
+
+ # Load defaults ?
+ if not doc or default_version > settings_version:
+ doc = default_doc
+
+ # Load client rules
+ for el in doc.getElementsByTagName("rule"):
+ client = Client(self, "")
+ client.from_xml(el)
+ if client.role:
+ log.info("load rule : %s " % client.description)
+ self.pa.clients[client.name] = client
+ #self.plugin_manager.check_client( client )
+
+
+ # Load sink rules
+ for el in doc.getElementsByTagName("output"):
+ sink = Sink(el.getAttribute("name"), int(el.getAttribute("priority")) )
+ self.pa.sinks[sink.name] = sink
+ #self.plugin_manager.check_client( client )
+
+
+ self.fade_timer_speed = float(doc.documentElement.getAttribute("fade_timer_speed"))
+ #self.mute_level = float(doc.documentElement.getAttribute("mute_level"))
+ #if doc.documentElement.hasAttribute("tray_visible"):
+ # self.status_icon.set_visible( doc.documentElement.getAttribute("tray_visible") == "True" )
+ #if doc.documentElement.hasAttribute("prefer_sink"):
+ # self.prefer_sink = doc.documentElement.getAttribute("prefer_sink")
+ #if doc.documentElement.hasAttribute("follow_new_outputs"):
+ # self.follow_new_outputs = doc.documentElement.getAttribute("follow_new_outputs") == "True"
+ #if doc.documentElement.hasAttribute("mute_phone"):
+ # self.mute_phone = doc.documentElement.getAttribute("mute_phone") == "True"
+
+
+ @threaded
+ def apply_volume_thread(self):
+ while True:
+ #logging.debug(": apply_volume_thread " )
+ if self.active:
+ self.apply_volume_thread_running = True
+ gobject.idle_add(self.__adjust_volumes)
+ else:
+ self.apply_volume_thread_running = False
+ time.sleep(self.fade_timer_speed)
+
+ @threaded
+ def select_client_thread(self):
+ while True:
+ if self.active:
+ self.select_client_thread_running = True
+ gobject.idle_add(self.set_active_clients)
+ else:
+ self.select_client_thread_running = False
+ time.sleep(0.5)
+
+ def __adjust_volumes(self):
+ # Always update based on active sinks
+ for sink in self.pa.sink_inputs.values():
+ if sink.set_volume():
+ # set pa volume
+ self.pa.pa_context_set_sink_input_volume(sink.index, sink.volume)
+
+ def __set_window_stack(self, active_client):
+ for client in self.pa.clients.values():
+
+ # Select the primary client and order previous clients
+ for key in self.priority_stack.keys():
+ if client.role == key:
+ # Add client to role
+ if not client in self.priority_stack[key]:
+ if active_client == client:
+ self.priority_stack[key].insert(0, client)
+ else:
+ self.priority_stack[key].append(client)
+
+ # reshuffle if active
+ elif active_client == client and not self.priority_stack[key][0] == client:
+ self.priority_stack[key].remove(client)
+ self.priority_stack[key].insert(0, client)
+
+ # If role has changed remove old entry
+ elif client in self.priority_stack[key]:
+ self.priority_stack[key].remove(client)
+
+ def print_stacks(self):
+ for key in self.priority_stack.keys():
+ if key:
+ text =""
+ for client in self.priority_stack[key]:
+ text += client.description
+ text += " ("
+ if client.is_active():
+ text += "*"
+ else:
+ text += str(len(client.sinks.values()))
+ text += "), "
+ if text: log.debug("stack " + key + " " + text)
+
+ def set_active_clients(self):
+
+ # Based on per sink stacks
+ for sink in self.pa.sinks.values():
+
+ # find the top ranking sink_input for this sink
+ highest_sink_input = None
+ highest_stack_media_role_index = -1
+ highest_stack_media_role_window_index = -1
+
+ for sink_input in self.pa.sink_inputs.values():
+ status = False
+
+ if sink_input.is_active():
+
+ # check against media role
+ if sink_input.role and sink_input.role in self.priority_stack.keys():
+
+ index = self.priority_stack.keys().index(sink_input.role)
+ if index <= highest_stack_media_role_index or highest_stack_media_role_index == -1:
+
+ # check window stack for client
+ if sink_input.client in self.priority_stack[sink_input.role]:
+ window_index = self.priority_stack[sink_input.role].index(sink_input.client)
+ if window_index <= highest_stack_media_role_window_index or highest_stack_media_role_window_index == -1:
+
+ # Set previous highest to inactive
+ if highest_sink_input:
+ highest_sink_input.set_status(False)
+
+ # Set new sink to active
+ highest_sink_input = sink_input
+ highest_stack_media_role_index = index
+ highest_stack_media_role_window_index = window_index
+ status = True
+
+ else:
+ # unknown media role so leave it always on
+ status = True
+
+ sink_input.set_status(status)
+ #self.print_stacks()
+
+ def on_active_window_change(self, application, state): #, pid, window_name, x, y, icon, fullscreen
+ if application:
+ if state in ("active","open"):
+ for client in self.pa.clients.values():
+ if self.match_client_to_application(client, application, state):
+ log.info(": window to client match %s => %s" % (application.name, client.description) )
+ self.__set_window_stack(client)
+ break
+
+
+ def match_client_to_application(self, client, application, index=0):
+ if client.test_focus_window(application.pid, application.window_name, application.command, application.name):
+ client.fullscreen = application.fullscreen
+ if not client.icon : client.icon = application.icon
+ if not client.icon_name : client.icon_name = application.icon_name
+ if not client.description :client.description = application.description
+ if not client.role: client.role = application.role
+ client.application = application
+
+ return True
+ return False
+
+ def set_auto_start(self, flag):
+ filename = "earcandy.desktop"
+ path = os.path.expanduser(os.getenv('XDG_CONFIG_HOME', '~/.config/autostart'))
+ dest = os.path.join(path, filename)
+ src = "/usr/share/applications/earcandy.desktop"
+
+ if flag:
+ # insure the autostart path exists
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ if not os.path.exists( dest ):
+ shutil.copyfile(src, dest)
+ else:
+ if os.path.exists( dest ):
+ os.remove( dest )
+
+ def is_auto_start(self):
+ filename = "earcandy.desktop"
+ dest = os.path.expanduser(os.getenv('XDG_CONFIG_HOME', '~/.config/autostart/' + filename))
+ return os.path.exists(dest)
+
+ def open_preferances(self):
+ self.print_stacks()
+ if not self.pref:
+ self.pref = EarCandayPrefs(self)
+ self.pref.run()
+
+ def close_preferances(self):
+ if self.pref:
+ self.pref = None
+
+ def get_current_sink_volume(self):
+ self.pa.get_sink_info_by_name(self.pa.prefer_sink_index)
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=logging.INFO)
+ import gtk
+ # Turn on gtk threading
+ gtk.gdk.threads_init()
+
+ ec = EarCandy()
+ ec.run()
+
+ gtk.main()
diff --git a/ear_candy/EarCandyDBus.py b/earcandy/EarCandyDBus.py
similarity index 100%
rename from ear_candy/EarCandyDBus.py
rename to earcandy/EarCandyDBus.py
diff --git a/earcandy/PulseAudio.py b/earcandy/PulseAudio.py
new file mode 100644
index 0000000..44d1b24
--- /dev/null
+++ b/earcandy/PulseAudio.py
@@ -0,0 +1,254 @@
+# Ear Candy - Pulseaduio sound managment tool
+# Copyright (C) 2008 Jason Taylor
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import logging
+import ctypes
+from pulseaudio.PulseAudioConnection import PulseAudioConnection
+from Client import Client
+from SinkInput import SinkInput
+from Sink import Sink
+
+log = logging.getLogger('EarCandyPulseAudio')
+log.setLevel(logging.INFO)
+
+class PulseAudio(PulseAudioConnection):
+ def __init__(self, Core):
+
+ self.core = Core
+ self.prefer_sink_index = 0
+ self.prefer_source_index = 0
+
+ self.sink = {} # sinks by name
+ self.sinks_by_id = {} # sinks by id
+ self.clients = {} # clients by name
+ self.clients_by_id = {} # clients by id
+ self.sink_inputs = {} # sink inputs by id
+ self.source_outputs = {} # source outputs by id
+
+ PulseAudioConnection.__init__(self, "EarCandy")
+
+ # CONNECTION
+ def pa_status_ready(self):
+ # list sinks again now that sink_inputs have been loaded
+ # this will trigger auto usb selection
+ self.pa_context_get_sink_info_list()
+ self.pa_context_get_source_info_list()
+ self.pa_context_get_source_output_info_list()
+
+
+ ### DEAL WITH SINKS #################################
+ def pa_sink_info_cb(self, sink, user_data):
+ s = None
+ if not sink.name in self.sinks.keys():
+ s = Sink(sink.name, 0, sink.index)
+ log.info("new sink : %s %s" % (sink.description, sink.index))
+ self.sinks[sink.name] = s
+
+ else:
+ s = self.sinks[sink.name]
+
+ self.sinks_by_id[sink.index] = s
+ s.description = sink.description
+ s.monitor_source = sink.monitor_source
+ s.monitor_source_name = sink.monitor_source_name
+ s.index = sink.index
+ s.volume = sink.volume
+
+ if s.name.lower().count("usb") > 0 or s.name.lower().count("bluetooth") > 0:
+ self.core.notify("New sound device", "Moving sound to new device")
+ log.info("new device detected moving sink inputs : %s " % sink.description)
+
+ self.prefer_sink_index = sink.index
+ self.pa_context_set_default_sink(sink.name)
+ self.move_all_sinks_inputs(sink.index)
+
+ if self.core.pref:
+ self.core.pref.update_output(s, s.priority)
+
+ def pa_sink_remove_cb(self, index):
+ sink = self.sinks_by_id[index]
+ sink.index = 0
+ if self.core.pref:
+ self.core.pref.update_output(sink, False)
+ return
+
+ def move_all_sinks_inputs(self, sink_index):
+ for sink_input in self.sink_inputs.values():
+ self.pa_context_move_sink_input_by_index(sink_input.index, sink_index)
+
+ def move_client_to_sink(self, client):
+ sink_index = self.prefer_sink_index
+ if client.prefer_sink:
+ for sink in self.sinks_by_id.values():
+ if sink.name == client.prefer_sink:
+ sink_index = sink.index
+ break
+ for sink_input in client.sinks.values():
+ self.pa_context_move_sink_input_by_index(sink_input.index, sink_index)
+
+ ### DEAL WITH SINKS #################################
+
+
+ ### DEAL WITH SOURCES ###############################
+ def pa_source_info_cb(self, source, user_data, s):
+ if source.name.lower().count("usb") > 0 or source.name.lower().count("bluetooth") > 0:
+ self.core.notify("New microphone", "Moving input to new device")
+ self.prefer_source_index = source.index
+ self.pa_context_set_default_source(source.name)
+ self.move_all_source_outputs(source.index)
+
+ def pa_source_output_info_cb(self, source_output, user_data):
+
+ if self.prefer_source_index > 0 and source_output.source != self.prefer_source_index:
+ self.pa_context_move_source_output_by_index(source_output.index, self.prefer_source_index)
+
+ self.source_outputs[source_output.index] = source_output
+
+ print "SOURCE OUTPUT" , source_output.name, source_output.source
+
+ def move_all_source_outputs(self, source_index):
+ for source_output in self.source_outputs.values():
+ self.pa_context_move_source_output_by_index(source_output.index, source_index)
+
+ ### DEAL WITH SOURCES ###############################
+
+
+ # CLIENTS
+ def pa_client_info_cb(self, client, user_data):
+
+ if not self.clients.has_key(client.name):
+ c = Client(self.core, client.name, client.pid)
+ log.info("new client : %s" % c.description)
+
+ self.clients[client.name] = c
+ else:
+ log.debug("update client : %s" % client.name)
+ c = self.clients[client.name]
+ c.pid = client.pid
+ self.clients_by_id[client.index] = c
+
+ def pa_client_remove_cb(self, index):
+
+ if self.clients_by_id.has_key(index):
+ client = self.clients_by_id[index]
+ log.debug("remove client : %s" % client.name)
+ del self.clients_by_id[index]
+
+ # SINK INPUTS
+ def pa_sink_input_info_cb(self, sink_input, user_data):
+ target_sink = self.prefer_sink_index
+
+ if not self.sink_inputs.has_key(sink_input.index):
+ s = SinkInput(sink_input.index, sink_input.name, sink_input.volume, sink_input.sink, sink_input.media_role)
+ self.sink_inputs[s.index] = s
+
+ if self.clients_by_id.has_key(sink_input.client):
+ client = self.clients_by_id[sink_input.client]
+ client.sinks[s.index] = s
+ s.client = client
+
+ # Skim client role from 1st stream
+ if not client.role and sink_input.media_role:
+ client.role = sink_input.media_role
+ log.info("new client media role : %s %s " % (client.name, sink_input.media_role))
+
+ self.pa_create_monitor_stream_for_sink_input(sink_input.index, self.sinks_by_id[sink_input.sink].monitor_source, sink_input.name, s.client.description)
+
+ log.info("new sink input : %s %s " % (s.client.description, s.name))
+
+ else:
+ log.debug("update sink input : %s %s" % (sink_input.name, sink_input.sink))
+ s = self.sink_inputs[sink_input.index]
+ s.volume = sink_input.volume
+ s.name = sink_input.name
+
+ # Check we have the correct sink
+ if s.client.prefer_sink:
+ for sink in self.sinks_by_id.values():
+ if sink.name == s.client.prefer_sink:
+ target_sink = sink.index
+
+ if not s.sink == sink_input.sink:
+ self.pa_create_monitor_stream_for_sink_input(sink_input.index, self.sinks_by_id[sink_input.sink].monitor_source, sink_input.name, s.client.description)
+ s.sink = sink_input.sink
+
+ if target_sink > 0 and self.sink_inputs[sink_input.index].sink != target_sink:
+ self.pa_context_move_sink_input_by_index(sink_input.index, target_sink)
+
+ # recheck clients to windows
+ self.core.window_watcher.check_all()
+
+ def pa_sink_input_remove_cb(self, index):
+
+ if self.sink_inputs.has_key(index):
+ sink = self.sink_inputs[index]
+ log.debug("remove sink input : %s" % sink.name)
+
+ del( sink.client.sinks[index] )
+ del( self.sink_inputs[index] )
+
+ # reset volume in pa
+ v = []
+ for i in range(0, len(sink.volume)):
+ v.append(100)
+ self.pa_context_set_sink_input_volume(index, v)
+
+ if self.core.pref:
+ self.core.pref.update_client( sink.client )
+
+
+ # VOLUME METER FOR SINK INPUT
+ def pa_stream_request_cb(self, meter_level, user_data):
+ if user_data in self.sink_inputs.keys():
+ self.sink_inputs[user_data].set_meter( meter_level )
+ log.debug("pa_stream_request_cb : %s %s" % (meter_level, user_data))
+ if self.core.pref:
+ self.core.pref.update_client( self.sink_inputs[user_data].client )
+
+ def pa_create_monitor_stream_for_sink_input(self, index, monitor_index, name, description):
+
+ log.debug("pa_create_monitor_stream_for_sink_input : %s" % index)
+
+ # Create new stream
+ ss = self.pa_sample_spec()
+ ss.channels = 1
+ ss.format = 5
+ ss.rate = 25
+ pa_stream = self.pa_stream_new(description, ss)
+
+ self.pa_stream_set_monitor_stream(pa_stream, index);
+ self.pa_stream_set_read_callback(pa_stream, index);
+ self.pa_stream_set_suspended_callback(pa_stream);
+
+ attr = self.pa_buffer_attr()
+ attr.fragsize = 4
+ attr.maxlength = 10
+ attr.tlength = 0
+ attr.prebuf = 0
+ attr.minreq = 0
+
+ self.pa_stream_connect_record(pa_stream, str(monitor_index), attr, 10752)
+
+
+if __name__ == '__main__':
+
+ import gtk
+ # Turn on gtk threading
+ gtk.gdk.threads_init()
+
+ pa = PulseAudio("Test")
+ pa.connect()
+
+ gtk.main()
diff --git a/runner/ear_candy b/earcandy/Sink.py
old mode 100755
new mode 100644
similarity index 64%
rename from runner/ear_candy
rename to earcandy/Sink.py
index 7faf7a4..340b392
--- a/runner/ear_candy
+++ b/earcandy/Sink.py
@@ -1,6 +1,5 @@
-#!/usr/bin/env python
# Ear Candy - Pulseaduio sound managment tool
-# Copyright (C) 2008 Jason Taylor, Michael Budde
+# Copyright (C) 2008 Jason Taylor
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -14,10 +13,20 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-import ear_candy
-import subprocess
-import os, sys
-if __name__ == "__main__":
- path = os.path.join(os.path.dirname(ear_candy.__file__), "ear_candy.py")
- subprocess.call([sys.executable, path] + sys.argv[1:])
+import math
+import time
+import datetime
+
+class Sink():
+ def __init__(self, name, priority=0, index=0):
+
+ self.name = name
+ self.priority = priority
+
+
+ self.index = index
+ self.monitor_source = 0
+ self.monitor_source_name = ""
+ self.description = name
+ self.volume = None
diff --git a/earcandy/SinkInput.py b/earcandy/SinkInput.py
new file mode 100644
index 0000000..78d7a2a
--- /dev/null
+++ b/earcandy/SinkInput.py
@@ -0,0 +1,144 @@
+# Ear Candy - Pulseaduio sound managment tool
+# Copyright (C) 2008 Jason Taylor
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import math
+import time
+import datetime
+
+class SinkInput():
+ def __init__(self, index, name, volume, sink, role):
+
+ self.index = index
+ self.name = name
+ self.client = None
+ self.volume = volume
+ self.sink = sink
+ self.__role = role
+
+ self.volume_meter_level = 0
+ self.volume_meter_last_non_zero_timestamp = time.mktime(datetime.datetime.min.timetuple())
+
+ self.volume_target = 100
+
+ self.__previous_volume = 0
+
+ self.volume_check = True
+ self.__status = False
+
+
+ def get_media_role(self):
+ if not self.__role: return self.client.role
+ return self.__role
+
+ def set_media_role(self, role):
+ self.__role = role
+
+ role = property(get_media_role, set_media_role)
+
+
+ def set_meter(self, meter_level):
+ timestamp = math.floor(time.mktime(datetime.datetime.now().timetuple()))
+ if(meter_level > 0):
+ self.volume_meter_last_non_zero_timestamp = timestamp
+ self.volume_meter_level = meter_level
+
+ def is_active(self):
+ timestamp = math.floor(time.mktime(datetime.datetime.now().timetuple()))
+ compare = timestamp - self.volume_meter_last_non_zero_timestamp
+ if compare <= 1:
+ return True
+ return False
+
+
+
+ ## new per sink settings ##
+ def set_volume(self):
+
+ current_volume = round(self.volume[0])
+ step_volume = current_volume
+ result = True
+
+ if current_volume < self.volume_target:
+ step_volume = current_volume + self.client.volume_step
+ if not self.client.fade_volume: step_volume = self.volume_target
+ elif current_volume > self.volume_target:
+ step_volume = current_volume - self.client.volume_step
+ if not self.client.fade_volume: step_volume = self.volume_target
+
+ if step_volume > 100:
+ step_volume = 100
+ if step_volume < self.client.volume_step:
+ step_volume = self.client.volume_step
+
+ # we dont want to get stuck in a loop because volumes arn't exactly the same
+ result = math.fabs(self.__previous_volume - current_volume) >= self.client.volume_step
+ volume_check = math.fabs(self.client.volume_target - current_volume) < self.client.volume_step
+
+ if result:
+ for i in range(0, len(self.volume)):
+ self.volume[i] = step_volume
+
+ #print "\nAdjust Volume", self.client.name, step_volume
+
+ if volume_check and not self.volume_check:
+ self.volume_check = volume_check
+ self.client.check_volume()
+
+ self.volume_check = volume_check
+
+ self.__previous_volume = step_volume
+ return result
+
+ def get_volume_meter(self):
+ return sink.volume_meter_level
+
+ def __fade_in(self):
+ self.volume_target = self.client.volume_max
+
+ def __fade_out(self):
+ if self.client.volume_min == -1:
+ self.volume_target = self.client.core.mute_level
+ else:
+ self.volume_target = self.client.volume_min
+
+ def __fade_mute(self):
+ if self.client.volume_min == -1:
+ self.volume_target = self.client.core.mute_level
+ else:
+ self.volume_target = self.client.volume_min
+
+ # if we goto 0 than our volume meter will never register a value
+ if self.volume_target < self.client.volume_step:
+ self.volume_target = self.client.volume_step
+
+ def set_status(self, value):
+
+ if value or self.role == "default":
+ self.__fade_in()
+ else:
+ self.__fade_mute()
+
+ self.__status = value
+
+ def get_status(self):
+ return self.__status
+
+
+
+
+
+
+
diff --git a/ear_candy/pulseaudio/__init__.py b/earcandy/__init__.py
similarity index 100%
copy from ear_candy/pulseaudio/__init__.py
copy to earcandy/__init__.py
diff --git a/earcandy/earcandyconfig.py b/earcandy/earcandyconfig.py
new file mode 100644
index 0000000..c1a4778
--- /dev/null
+++ b/earcandy/earcandyconfig.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+### BEGIN LICENSE
+# This file is in the public domain
+### END LICENSE
+
+# THIS IS Earcandy CONFIGURATION FILE
+# YOU CAN PUT THERE SOME GLOBAL VALUE
+# Do not touch until you know what you're doing.
+# you're warned :)
+
+# where your project will head for your data (for instance, images and ui files)
+# by default, this is ../data, relative your trunk layout
+__earcandy_data_directory__ = '../data/'
+
+
+import os
+
+class project_path_not_found(Exception):
+ pass
+
+def getdatapath():
+ """Retrieve earcandy data path
+
+ This path is by default <earcandy_lib_path>/../data/ in trunk
+ and /usr/share/earcandy in an installed version but this path
+ is specified at installation time.
+ """
+
+ # get pathname absolute or relative
+ if __earcandy_data_directory__.startswith('/'):
+ pathname = __earcandy_data_directory__
+ else:
+ pathname = os.path.dirname(__file__) + '/' + __earcandy_data_directory__
+
+ abs_data_path = os.path.abspath(pathname)
+ if os.path.exists(abs_data_path):
+ return abs_data_path
+ else:
+ raise project_path_not_found
+
diff --git a/ear_candy/window/__init__.py b/earcandy/plugins/banshee/__init__.py
similarity index 100%
rename from ear_candy/window/__init__.py
rename to earcandy/plugins/banshee/__init__.py
diff --git a/earcandy/plugins/banshee/plugin.py b/earcandy/plugins/banshee/plugin.py
new file mode 100644
index 0000000..ff3f02f
--- /dev/null
+++ b/earcandy/plugins/banshee/plugin.py
@@ -0,0 +1,57 @@
+# Ear Candy - Pulseaduio sound managment tool
+# Copyright (C) 2009 Jason Taylor
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from PluginBase import PluginBaseObject
+
+import dbus
+
+class BansheeControlPlugin(PluginBaseObject):
+
+ def __init__(self, core, path):
+ PluginBaseObject(core, path)
+ self.bus = dbus.SessionBus()
+ return
+
+ def get_client_name(self):
+ return "Banshee"
+
+ def get_plugin_name(self):
+ return "Banshee pause control (DBUS)"
+
+ # Is application playing
+ def is_playing(self):
+ banshee = self.__get_application()
+ if banshee:
+ return not banshee.GetCurrentState() == "paused"
+ return False
+
+ # Set application paused status
+ def set_pause(self, status):
+ banshee = self.__get_application()
+ if banshee:
+ if status:
+ banshee.Pause()
+ else:
+ banshee.Play()
+ return True
+ return False
+
+ def __get_application(self):
+ return self.bus.get_object("org.bansheeproject.Banshee", "/org/bansheeproject/Banshee/PlayerEngine")
+
+
+def register_plugin( core, path ):
+ return BansheeControlPlugin( core, path )
diff --git a/ear_candy/pulseaudio/__init__.py b/earcandy/plugins/rhythmbox/__init__.py
similarity index 100%
copy from ear_candy/pulseaudio/__init__.py
copy to earcandy/plugins/rhythmbox/__init__.py
diff --git a/earcandy/plugins/rhythmbox/plugin.py b/earcandy/plugins/rhythmbox/plugin.py
new file mode 100644
index 0000000..97d4526
--- /dev/null
+++ b/earcandy/plugins/rhythmbox/plugin.py
@@ -0,0 +1,54 @@
+# Ear Candy - Pulseaduio sound managment tool
+# Copyright (C) 2009 Jason Taylor
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from PluginBase import PluginBaseObject
+
+import dbus
+
+class RhythmboxControlPlugin(PluginBaseObject):
+
+ def __init__(self, core, path):
+ PluginBaseObject(core, path)
+ self.bus = dbus.SessionBus()
+ return
+
+ # Client this plugin applies to
+ def get_client_name(self):
+ return "Rhythmbox"
+
+ def get_plugin_name(self):
+ return "Rhythmbox pause control (DBUS)"
+
+ def __get_application(self):
+ return self.bus.get_object("org.gnome.Rhythmbox", "/org/gnome/Rhythmbox/Player")
+
+ # Is application paused
+ def is_playing(self):
+ rb = self.__get_application()
+ if rb:
+ return rb.getPlaying()
+ return False
+
+ # Set application paused status
+ def set_pause(self, status):
+ rb = self.__get_application()
+ if rb:
+ rb.playPause(status)
+ return True
+ return False
+
+def register_plugin( core, path ):
+ return RhythmboxControlPlugin( core, path )
diff --git a/earcandy/pulseaudio/Client.py b/earcandy/pulseaudio/Client.py
new file mode 100644
index 0000000..f623988
--- /dev/null
+++ b/earcandy/pulseaudio/Client.py
@@ -0,0 +1,12 @@
+
+class Client():
+ def __init__(self, pa, contents):
+
+ self.name = str(contents.name)
+ self.index = int(contents.index)
+ self.pid = float(pa.pa_proplist_gets(contents.proplist, "application.process.id") or -1)
+
+ # Almost no applications use these yet :(
+ self.binary = pa.pa_proplist_gets(contents.proplist, "application.process.binary") or None
+ self.xid = pa.pa_proplist_gets(contents.proplist, "window.x11.xid") or -1
+ self.window_id = pa.pa_proplist_gets(contents.proplist, "window.id") or -1
diff --git a/earcandy/pulseaudio/Crash.py b/earcandy/pulseaudio/Crash.py
new file mode 100644
index 0000000..98239eb
--- /dev/null
+++ b/earcandy/pulseaudio/Crash.py
@@ -0,0 +1,24 @@
+
+
+from PulseAudioConnection import PulseAudioConnection
+
+
+class PulseAudio(PulseAudioConnection):
+ def __init__(self, Name):
+
+ PulseAudioConnection.__init__(self, Name)
+
+ def pa_sink_input_info_cb(self, sink_input, user_data):
+ #print self.convert_simple_volume_to_pa_volume(sink_input.volume).values[0]
+ self.pa_context_set_sink_input_volume(sink_input.index, sink_input.volume )
+ return
+
+if __name__ == '__main__':
+ import gtk
+ # Turn on gtk threading
+ gtk.gdk.threads_init()
+
+ pa = PulseAudio("Test")
+ pa.connect()
+
+ gtk.main()
diff --git a/earcandy/pulseaudio/PulseAudioConnection.py b/earcandy/pulseaudio/PulseAudioConnection.py
new file mode 100644
index 0000000..d05e512
--- /dev/null
+++ b/earcandy/pulseaudio/PulseAudioConnection.py
@@ -0,0 +1,363 @@
+# Ear Candy - Pulseaduio sound managment tool
+# Copyright (C) 2008 Jason Taylor
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from lib_pulseaudio import *
+import gobject
+import ctypes
+import logging
+from Client import Client
+from SinkInput import SinkInput
+from Sink import Sink
+from SourceOutput import SourceOutput
+from Source import Source
+PA_VOLUME_CONVERSION_FACTOR = 655.36
+
+log = logging.getLogger('PulseAudioConnection')
+log.setLevel(logging.DEBUG)
+
+# A null method that can be given to pulse methods
+def null_cb(a=None, b=None, c=None, d=None):
+ #print "NULL CB"
+ return
+
+class PulseAudioConnection():
+
+ def __init__(self, Name="Simple Pulse Audio Connection"):
+
+ self.Name = Name
+
+ self.sinks = {}
+ self.monitor_sinks = []
+ self.module_stream_restore_argument = ""
+
+ def connect(self):
+
+ self.__mainloop = pa_threaded_mainloop_new()
+ self.__mainloop_api = pa_threaded_mainloop_get_api(self.__mainloop)
+
+ self._context = pa_context_new(self.__mainloop_api, self.Name )
+
+ self._context_notify_cb = pa_context_notify_cb_t(self.context_notify_cb)
+ pa_context_set_state_callback(self._context, self._context_notify_cb, None)
+
+ pa_context_connect(self._context, None, 0, None);
+ pa_threaded_mainloop_start(self.__mainloop)
+
+ def disconnect(self):
+ pa_context_disconnect(self._context)
+
+ # pulseaudio connection status
+ def context_notify_cb(self, context, userdata):
+
+ try:
+ ctc = pa_context_get_state(context)
+ if ctc == PA_CONTEXT_READY:
+ log.debug("connection ready")
+
+ self._null_cb = pa_context_success_cb_t(null_cb)
+ self._pa_context_success_cb = pa_context_success_cb_t(self.__pa_context_success_cb)
+ self._pa_stream_request_cb = pa_stream_request_cb_t(self.__pa_stream_request_cb)
+ self._pa_stream_notify_cb = pa_stream_notify_cb_t(self.__pa_stream_request_cb)
+ self._pa_sink_info_cb = pa_sink_info_cb_t(self.__pa_sink_info_cb)
+ self._pa_context_subscribe_cb = pa_context_subscribe_cb_t(self.__pa_context_subscribe_cb)
+ self._pa_source_info_cb = pa_source_info_cb_t(self.__pa_source_info_cb)
+ self._pa_source_output_info_cb = pa_source_output_info_cb_t(self.__pa_source_output_info_cb)
+ self._pa_sink_input_info_list_cb = pa_sink_input_info_cb_t(self.__pa_sink_input_info_cb)
+ self._pa_client_info_list_cb = pa_client_info_cb_t(self.__pa_client_info_cb)
+ self._pa_module_info_cb = pa_module_info_cb_t(self.__pa_module_info_cb)
+ self._pa_context_index_cb = pa_context_index_cb_t(self.__pa_context_index_cb)
+
+ o = pa_context_get_module_info_list(self._context, self._pa_module_info_cb, True)
+ pa_operation_unref(o)
+
+ o = pa_context_get_source_info_list(self._context, self._pa_source_info_cb, True)
+ pa_operation_unref(o)
+
+ o = pa_context_get_sink_info_list(self._context, self._pa_sink_info_cb, True)
+ pa_operation_unref(o)
+
+ o = pa_context_get_client_info_list(self._context, self._pa_client_info_list_cb, None)
+ pa_operation_unref(o)
+
+ o = pa_context_get_source_output_info_list(self._context, self._pa_source_output_info_cb, None)
+ pa_operation_unref(o)
+
+ o = pa_context_get_sink_input_info_list(self._context, self._pa_sink_input_info_list_cb, True)
+ pa_operation_unref(o)
+
+ pa_context_set_subscribe_callback(self._context, self._pa_context_subscribe_cb, None);
+ o = pa_context_subscribe(self._context, (pa_subscription_mask_t)
+ (PA_SUBSCRIPTION_MASK_SINK |
+ PA_SUBSCRIPTION_MASK_SOURCE |
+ PA_SUBSCRIPTION_MASK_SINK_INPUT |
+ PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT |
+ PA_SUBSCRIPTION_MASK_CLIENT |
+ PA_SUBSCRIPTION_MASK_SERVER), self._null_cb, None)
+
+ pa_operation_unref(o)
+ gobject.idle_add(self.pa_status_ready)
+
+ if ctc == PA_CONTEXT_FAILED :
+ log.debug("connection failed")
+ pa_threaded_mainloop_signal(self.__mainloop, 0)
+
+ if ctc == PA_CONTEXT_TERMINATED:
+ log.debug("connection terminated")
+ pa_threaded_mainloop_signal(self.__mainloop, 0)
+
+ except Exception, text:
+ log.exception("context_notify_cb %s" % text)
+
+ def __pa_context_index_cb(self, context, index, user_data):
+ log.debug("__pa_context_index_cb")
+ gobject.idle_add(self.pa_context_index_cb, index, user_data)
+ return
+
+ def __pa_module_info_cb(self, context, struct, eol, user_data):
+
+ if struct:
+ log.debug("__pa_module_info_cb : %s" % struct.contents.name)
+ gobject.idle_add(self.pa_module_info_cb, struct.contents, eol, user_data)
+
+ def __pa_source_info_cb(self, context, struct, eol, user_data):
+ if struct:
+ source = Source(self, struct.contents)
+ log.debug("__pa_source_info_cb : %s" % struct.contents.name)
+ gobject.idle_add(self.pa_source_info_cb, source, eol, user_data)
+
+ def __pa_source_output_info_cb(self, context, struct, c_int, user_data):
+ if struct:
+ source_output = SourceOutput(self, struct.contents)
+ log.debug("__pa_source_output_info_cb : %s" % struct.contents.name)
+ gobject.idle_add(self.pa_source_output_info_cb, source_output, user_data)
+
+ def __pa_context_success_cb(self, context, c_int, user_data):
+ log.debug("__pa_source_output_info_cb")
+ gobject.idle_add(self.pa_context_success_cb, c_int, user_data)
+
+ def __pa_stream_request_cb(self, stream, length, user_data):
+ data = self.pa_stream_peek(stream, length)
+ v = data[length / 4 -1] * 100
+ if (v < 0):
+ v = 0
+ if (v > 100):
+ v = 100
+ self.pa_stream_drop(stream)
+
+ #log.debug("__pa_stream_request_cb")
+ gobject.idle_add(self.pa_stream_request_cb, v, user_data)
+
+ def __pa_context_subscribe_cb(self, context, event_type, index, user_data):
+
+ try:
+ et = event_type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK
+
+ if et == PA_SUBSCRIPTION_EVENT_CLIENT:
+
+ if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
+ log.debug("PA_SUBSCRIPTION_EVENT_CLIENT | PA_SUBSCRIPTION_EVENT_REMOVE : %d" % index)
+ gobject.idle_add(self.pa_client_remove_cb, int(index))
+ else:
+ o = pa_context_get_client_info(self._context, index, self._pa_client_info_list_cb, None)
+ pa_operation_unref(o)
+
+ if et == PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
+ log.debug("PA_SUBSCRIPTION_EVENT_SINK_INPUT | PA_SUBSCRIPTION_EVENT_REMOVE : %d" % index)
+ gobject.idle_add(self.pa_sink_input_remove_cb, int(index))
+ else:
+ o = pa_context_get_sink_input_info(self._context, int(index), self._pa_sink_input_info_list_cb, True)
+ pa_operation_unref(o)
+
+ if et == PA_SUBSCRIPTION_EVENT_SINK:
+ if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
+ log.debug("PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_REMOVE : %d" % index)
+ gobject.idle_add(self.pa_sink_remove_cb, int(index))
+ else:
+ o = pa_context_get_sink_info_by_index(self._context, int(index), self._pa_sink_info_cb, False)
+ pa_operation_unref(o)
+
+ if et == PA_SUBSCRIPTION_EVENT_SOURCE:
+ if event_type & PA_SUBSCRIPTION_EVENT_TYPE_MASK == PA_SUBSCRIPTION_EVENT_REMOVE:
+ log.debug("PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE : %d" % index)
+ gobject.idle_add(self.pa_source_remove_cb, int(index))
+ else:
+ o = pa_context_get_source_info_by_index(self._context, int(index), self._pa_source_info_cb, False)
+ pa_operation_unref(o)
+
+
+ except Exception, text:
+ log.exception("PA ERROR pa_context_subscribe_cb %s" % text)
+
+ def __pa_client_info_cb(self, context, struct, c_int, user_data):
+ if struct:
+ client = Client(self, struct.contents)
+ log.debug("__pa_client_info_cb : %s" % struct.contents.name)
+ gobject.idle_add(self.pa_client_info_cb, client, user_data)
+
+ def __pa_sink_input_info_cb(self, context, struct, index, user_data):
+ if struct:
+ sink_input = SinkInput(self, struct.contents)
+ log.debug("__pa_sink_input_info_cb : %s" % struct.contents.name)
+ gobject.idle_add( self.pa_sink_input_info_cb, sink_input, user_data)
+
+ def __pa_sink_info_cb(self, context, struct, index, user_data):
+ if struct:
+ sink = Sink(self, struct.contents)
+ log.debug("__pa_sink_info_cb : %s" % struct.contents.name)
+ gobject.idle_add( self.pa_sink_info_cb, sink, user_data)
+
+ # EVENTS
+ def pa_client_info_cb(self, client, user_data):
+ return
+ def pa_client_remove_cb(self, index):
+ return
+
+ def pa_sink_input_info_cb(self, sink_input, user_data):
+ return
+ def pa_sink_input_remove_cb(self, index):
+ return
+
+ def pa_sink_info_cb(self, sink, user_data):
+ return
+ def pa_sink_remove_cb(self, index):
+ return
+
+ def pa_source_info_cb(self, contents, eol, user_data):
+ return
+ def pa_source_remove_cb(self, index):
+ return
+
+ def pa_module_info_cb(self, contents, eol, user_data):
+ return
+ def pa_source_output_info_cb(self, contents, index, user_data):
+ return
+ def pa_stream_request_cb(self, meter, index):
+ return
+ def pa_context_success_cb(self, c_int, user_data):
+ return
+ def pa_context_index_cb(self, index, user_data):
+ return
+
+ def pa_status_ready(self):
+ return
+
+
+ def pa_buffer_attr(self):
+ return pa_buffer_attr()
+ def pa_sample_spec(self):
+ return pa_sample_spec()
+ def pa_stream_new(self, name, sample_spec):
+ return pa_stream_new(self._context, name, sample_spec, None)
+ def pa_stream_set_monitor_stream(self, pa_stream, index):
+ return pa_stream_set_monitor_stream(pa_stream, index)
+ def pa_stream_set_read_callback(self, pa_stream, index):
+ return pa_stream_set_read_callback(pa_stream, self._pa_stream_request_cb, index)
+ def pa_stream_set_suspended_callback(self, pa_stream):
+ return pa_stream_set_suspended_callback(pa_stream, self._pa_stream_notify_cb, "DOOM")
+ def pa_stream_connect_record(self, pa_stream, monitor_index, attr, size):
+ return pa_stream_connect_record(pa_stream, monitor_index, attr, size)
+ def pa_stream_peek(self, stream, length):
+ data = POINTER(c_float)()
+ pa_stream_peek(stream, data, ctypes.c_ulong(length))
+ return data
+ def pa_stream_drop(self, stream):
+ pa_stream_drop(stream)
+
+
+ # METHODS
+ def pa_context_get_sink_info_list(self):
+ o = pa_context_get_sink_info_list(self._context, self._pa_sink_info_cb, True)
+ pa_operation_unref(o)
+
+ def pa_context_get_source_output_info_list(self):
+ o = pa_context_get_source_output_info_list(self._context, self._pa_source_output_info_cb, True)
+ pa_operation_unref(o)
+
+ def pa_context_get_source_info_list(self):
+ o = pa_context_get_source_info_list(self._context, self._pa_source_info_cb, True)
+ pa_operation_unref(o)
+
+ def pa_proplist_gets(self, proplist, name):
+ return pa_proplist_gets(proplist, name)
+
+ def convert_simple_volume_to_pa_volume(self, volume):
+ vol = pa_cvolume()
+ vol.channels = len(volume)
+ v = pa_volume_t * 32
+ vol.values = v()
+ for i in range(0, len(volume)):
+ if i == 32: return vol
+ channel_volume = volume[i]
+ if channel_volume < 0: channel_volume = 0
+ if channel_volume > 100: channel_volume = 100
+ vol.values[i] = int(channel_volume * PA_VOLUME_CONVERSION_FACTOR)
+ return vol
+
+ def convert_pa_volume_to_simple_volume(self, volume):
+ vol = []
+ for i in range(0, volume.channels):
+ vol.append( int(volume.values[i] / PA_VOLUME_CONVERSION_FACTOR) )
+ return vol
+
+ def pa_context_move_sink_input_by_index(self, sink_input_index, sink_index):
+ pa_context_move_sink_input_by_index(self._context, sink_input_index, sink_index, self._pa_context_success_cb, None)
+
+ def pa_context_move_sink_input_by_name(self, sink_index, output_name):
+ pa_context_move_sink_input_by_name(self._context, sink_index, output_name, self._pa_context_success_cb, None)
+
+ def pa_context_set_sink_volume_by_name(self, sink_name, volume):
+ vol = self.convert_simple_volume_to_pa_volume(volume)
+ o = pa_context_set_sink_volume_by_name(self._context, sink_name, vol, self._null_cb, None)
+ pa_operation_unref(o)
+
+ def pa_context_set_sink_input_volume(self, index, volume):
+ log.debug("pa_context_set_sink_input_volume : %s" % index)
+ vol = self.convert_simple_volume_to_pa_volume(volume)
+ o = pa_context_set_sink_input_volume(self._context, index, vol, self._null_cb, None) # NOTE: dont pass in any thing here causes a seg fault
+ pa_operation_unref(o)
+
+ def pa_context_get_sink_info_by_name(self, sink_name):
+ o = pa_context_get_sink_info_by_name(self._context, sink_name, self._pa_sink_info_cb, False)
+ pa_operation_unref(o)
+
+ def pa_context_get_source_info_by_name(self, source_name):
+ o = pa_context_get_source_info_by_name(self._context, source_name, self._pa_source_info_cb, False)
+ pa_operation_unref(o)
+
+ def pa_context_set_default_sink(self, sink_name):
+ o = pa_context_set_default_sink(self._context, sink_name, self._pa_context_success_cb, False)
+ pa_operation_unref(o)
+
+ def pa_context_move_source_output_by_index(self, source_output_index, source_index):
+ pa_context_move_source_output_by_index(self._context, source_output_index, source_index, self._pa_context_success_cb, None)
+
+ def pa_context_set_default_source(self, source_name):
+ o = pa_context_set_default_source(self._context, source_name, self._pa_context_success_cb, False)
+ pa_operation_unref(o)
+
+if __name__ == '__main__':
+
+ log.basicConfig(level=log.CRITICAL)
+ import gtk
+ # Turn on gtk threading
+ gtk.gdk.threads_init()
+
+ pac = PulseAudioConnection("Test")
+ pac.connect()
+
+ gtk.main()
+
diff --git a/earcandy/pulseaudio/Sink.py b/earcandy/pulseaudio/Sink.py
new file mode 100644
index 0000000..61969e1
--- /dev/null
+++ b/earcandy/pulseaudio/Sink.py
@@ -0,0 +1,11 @@
+
+class Sink():
+ def __init__(self, pa, contents):
+
+ self.name = str(contents.name)
+ self.index = int(contents.index)
+ self.volume = pa.convert_pa_volume_to_simple_volume(contents.volume)
+ self.description = pa.pa_proplist_gets(contents.proplist, "device.description")
+
+ self.monitor_source = int(contents.monitor_source)
+ self.monitor_source_name = str(contents.monitor_source_name)
diff --git a/earcandy/pulseaudio/SinkInput.py b/earcandy/pulseaudio/SinkInput.py
new file mode 100644
index 0000000..2198781
--- /dev/null
+++ b/earcandy/pulseaudio/SinkInput.py
@@ -0,0 +1,16 @@
+
+class SinkInput():
+ def __init__(self, pa, contents):
+
+ self.name = str(contents.name)
+ self.index = int(contents.index)
+ self.volume = pa.convert_pa_volume_to_simple_volume(contents.volume)
+
+ self.client = int(contents.client)
+ self.sink = int(contents.sink)
+
+ self.media_role = pa.pa_proplist_gets(contents.proplist, "media.role") or None
+ self.media_name = pa.pa_proplist_gets(contents.proplist, "media.name") or None
+ self.media_title = pa.pa_proplist_gets(contents.proplist, "media.title") or None
+ self.media_artist = pa.pa_proplist_gets(contents.proplist, "media.artist") or None
+ self.media_icon_name = pa.pa_proplist_gets(contents.proplist, "media.icon_name") or None
diff --git a/earcandy/pulseaudio/Source.py b/earcandy/pulseaudio/Source.py
new file mode 100644
index 0000000..4bbb1f2
--- /dev/null
+++ b/earcandy/pulseaudio/Source.py
@@ -0,0 +1,9 @@
+
+class Source():
+ def __init__(self, pa, contents):
+
+ self.name = str(contents.name)
+ self.index = int(contents.index)
+ #self.volume = pa.convert_pa_volume_to_simple_volume(contents.volume)
+ #self.description = pa.pa_proplist_gets(contents.proplist, "device.description")
+
diff --git a/earcandy/pulseaudio/SourceOutput.py b/earcandy/pulseaudio/SourceOutput.py
new file mode 100644
index 0000000..47126fe
--- /dev/null
+++ b/earcandy/pulseaudio/SourceOutput.py
@@ -0,0 +1,12 @@
+
+class SourceOutput():
+ def __init__(self, pa, contents):
+
+ self.name = str(contents.name)
+ self.index = int(contents.index)
+ #self.volume = pa.convert_pa_volume_to_simple_volume(contents.volume)
+
+ self.client = int(contents.client)
+ self.source = int(contents.source)
+
+
diff --git a/ear_candy/pulseaudio/__init__.py b/earcandy/pulseaudio/__init__.py
similarity index 100%
copy from ear_candy/pulseaudio/__init__.py
copy to earcandy/pulseaudio/__init__.py
diff --git a/ear_candy/pulseaudio/lib.py b/earcandy/pulseaudio/lib.py
similarity index 95%
rename from ear_candy/pulseaudio/lib.py
rename to earcandy/pulseaudio/lib.py
index 37eb3f8..35b020d 100644
--- a/ear_candy/pulseaudio/lib.py
+++ b/earcandy/pulseaudio/lib.py
@@ -32,13 +32,15 @@
import os
import re
import sys
-
+import logging
import ctypes
import ctypes.util
_debug_lib = False
_debug_trace = False
+logging.basicConfig(level=logging.DEBUG)
+
class LibraryLoader(object):
def load_library(self, *names, **kwargs):
'''Find and load a library.
@@ -64,10 +66,7 @@ class LibraryLoader(object):
for name in platform_names:
try:
lib = ctypes.cdll.LoadLibrary(name)
- #if _debug_lib:
- print "Loaded library :", name
- #if _debug_trace:
- # lib = _TraceLibrary(lib)
+ logging.debug("lib :: Loaded library : %s" % name)
return lib
except OSError:
path = self.find_library(name)
diff --git a/ear_candy/pulseaudio/lib_pulseaudio.py b/earcandy/pulseaudio/lib_pulseaudio.py
similarity index 99%
rename from ear_candy/pulseaudio/lib_pulseaudio.py
rename to earcandy/pulseaudio/lib_pulseaudio.py
index 6824457..bdf9e9d 100644
--- a/ear_candy/pulseaudio/lib_pulseaudio.py
+++ b/earcandy/pulseaudio/lib_pulseaudio.py
@@ -1928,7 +1928,7 @@ pa_proplist_to_string.argtypes = [
pa_ext_stream_restore_delete = _lib.pa_ext_stream_restore_delete
pa_ext_stream_restore_delete.restype = pa_operation
-pa_ext_stream_restore_delete.argtypes = [POINTER(pa_context), c_char_p, pa_context_success_cb_t, POINTER(None)]
+pa_ext_stream_restore_delete.argtypes = [POINTER(pa_context), POINTER(c_char_p), pa_context_success_cb_t, POINTER(None)]
diff --git a/earcandy/ui/Client.py b/earcandy/ui/Client.py
new file mode 100644
index 0000000..5f72dba
--- /dev/null
+++ b/earcandy/ui/Client.py
@@ -0,0 +1,85 @@
+
+import sys
+import gtk
+import time
+import gobject
+import os
+from earcandy.earcandyconfig import getdatapath
+from earcandy.util.Threads import threaded
+
+class Client:
+ def __init__(self, client, core):
+ self.core = core
+ self.client = client
+
+ glade = os.path.join(getdatapath(), 'ui', 'EarCandy.ui')
+
+ # Get glade file XML
+ f = open( glade ,"r")
+ xml = f.read()
+ f.close()
+
+ # Remember you will need to recreate tree everytime the window loads
+ #wtree = gtk.glade.xml_new_from_buffer(xml, len(xml), "table_clients")
+
+ wtree = gtk.Builder()
+ wtree.add_from_string(xml)
+
+ self.gtk = wtree.get_object("table_clients")
+ self.gtk_progressbar_meter = wtree.get_object("progressbar_meter")
+ self.gtk_label = wtree.get_object("label_client_name")
+ self.gtk_icon = wtree.get_object("image_client_icon")
+ self.gtk_role_combobox = wtree.get_object("combobox_role")
+ self.button_client = wtree.get_object("button_client")
+
+ store = gtk.ListStore(gobject.TYPE_STRING)
+ self.gtk_role_combobox.set_model(store)
+ cell = gtk.CellRendererText()
+ self.gtk_role_combobox.pack_start(cell, True)
+ self.gtk_role_combobox.add_attribute(cell, 'text', 0)
+
+ for key in self.core.display.keys():
+ self.gtk_role_combobox.append_text(self.core.display[key])
+ self.gtk_role_combobox.set_active(0)
+
+ #client.gtk_role_combobox.set_model(self.cb_model)
+ self.gtk_role_combobox.connect("changed", self.on_role_changed)
+ self.button_client.connect("clicked", self.on_clicked)
+
+ style = gtk.Style()
+ self.gtk_label.modify_fg(gtk.STATE_NORMAL, style.fg[gtk.STATE_NORMAL])
+
+ self.__hide_timer = False
+
+ self.gtk.unparent()
+
+ def on_clicked(self, widget):
+ self.core.pref.open_client_properties(self.client)
+
+
+ def on_role_changed(self, widget):
+ #self.client.role = ""
+ for key in self.core.display.keys():
+ if self.core.display[key] == widget.get_active_text():
+ self.client.role = key
+ self.core.save()
+ return
+
+ def set_status(self, status):
+ if not status:
+ if not self.__hide_timer:
+ self.__hide_timer = True
+ self.remove()
+ else:
+ self.__hide_timer = False
+
+ @threaded
+ def remove(self):
+ count = 1
+ while count < 60:
+ if not self.__hide_timer: return
+ time.sleep(1)
+ count = count + 1
+ if not self.client.is_active():
+ self.gtk.hide()
+
diff --git a/ear_candy/EarCandyAppSelect.py b/earcandy/ui/EarCandyAppSelect.py
similarity index 95%
rename from ear_candy/EarCandyAppSelect.py
rename to earcandy/ui/EarCandyAppSelect.py
index 4a0b4eb..3dee445 100644
--- a/ear_candy/EarCandyAppSelect.py
+++ b/earcandy/ui/EarCandyAppSelect.py
@@ -15,22 +15,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
-try:
- import pygtk
- pygtk.require("2.0")
-except:
- pass
-try:
- import gtk
- import gtk.glade
-except:
- sys.exit(1)
+
+import gtk
import re
import copy
from glade_window import GladeWindow
-from window.WindowWatcher import WindowWatcher
from Client import Client
class EarCandyAppSelect(GladeWindow):
diff --git a/ear_candy/EarCandyClientProperties.py b/earcandy/ui/EarCandyClientProperties.py
similarity index 56%
rename from ear_candy/EarCandyClientProperties.py
rename to earcandy/ui/EarCandyClientProperties.py
index b7965ec..150daad 100644
--- a/ear_candy/EarCandyClientProperties.py
+++ b/earcandy/ui/EarCandyClientProperties.py
@@ -15,23 +15,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
-try:
- import pygtk
- pygtk.require("2.0")
-except:
- pass
-try:
- import gtk
- import gtk.glade
-except:
- sys.exit(1)
-
+import gobject
+import gtk
import re
import copy
-
from glade_window import GladeWindow
-from window.WindowWatcher import WindowWatcher
-
from Client import Client
class EarCandyClientProperties(GladeWindow):
@@ -41,60 +29,67 @@ class EarCandyClientProperties(GladeWindow):
GladeWindow.__init__(self, "pulseoptions.glade", "window_client_properties", parent)
- self.entry_description = self.wtree.get_widget("entry_description")
- self.image_icon = self.wtree.get_widget("image_icon")
- self.hscale_max_volume = self.wtree.get_widget("hscale_max_volume")
- self.entry_window_title = self.wtree.get_widget("entry_window_title")
- self.entry_command = self.wtree.get_widget("entry_command")
- self.entry_application = self.wtree.get_widget("entry_application")
- self.label_name = self.wtree.get_widget("label_name")
- self.entry_name = self.wtree.get_widget("entry_name")
- self.combobox_category = self.wtree.get_widget("combobox_category")
- self.checkbutton_fix = self.wtree.get_widget("checkbutton_fix")
- self.checkbutton_fade_volume = self.wtree.get_widget("checkbutton_fade_volume")
- self.combobox_output = self.wtree.get_widget("combobox_output")
+ self.entry_description = self.wtree.get_object("entry_description")
+ self.image_icon = self.wtree.get_object("image_icon")
+ self.hscale_max_volume = self.wtree.get_object("hscale_max_volume")
+ self.hscale_min_volume = self.wtree.get_object("hscale_min_volume")
+ self.entry_window_title = self.wtree.get_object("entry_window_title")
+ self.entry_command = self.wtree.get_object("entry_command")
+ self.entry_application = self.wtree.get_object("entry_application")
+ self.label_name = self.wtree.get_object("label_name")
+ self.entry_name = self.wtree.get_object("entry_name")
+ self.combobox_category = self.wtree.get_object("combobox_category")
+ self.checkbutton_fix = self.wtree.get_object("checkbutton_fix")
+ self.checkbutton_fade_volume = self.wtree.get_object("checkbutton_fade_volume")
+ self.combobox_output = self.wtree.get_object("combobox_output")
signals = {
"on_button_close_clicked" : self.on_button_close_clicked,
"on_button_apply_clicked" : self.on_button_apply_clicked
}
- self.wtree.signal_autoconnect(signals)
+ self.wtree.connect_signals(signals)
self.entry_description.set_text(client.description)
self.window.set_title("Ear Candy : " + client.description)
-
- self.image_icon.set_from_pixbuf(client.icon)
-
- self.hscale_max_volume.set_value( client.volume_default )
-
+ self.image_icon.set_from_pixbuf(client.icon)
+ self.hscale_max_volume.set_value( client.volume_max )
+ self.hscale_min_volume.set_value( client.volume_min )
self.entry_window_title.set_text( client.rule_re_window_title.pattern )
-
self.entry_command.set_text( client.rule_re_command.pattern )
-
self.entry_application.set_text( client.rule_re_application.pattern )
-
self.label_name.set_markup("<b><big>" + client.description + "</big></b>")
-
self.entry_name.set_text(client.name)
-
self.checkbutton_fix.set_active( client.apply_volume_meter_hack )
-
self.checkbutton_fade_volume.set_active( client.fade_volume )
+
+ #store = gtk.ListStore(gobject.TYPE_STRING)
+ #self.combobox_output.set_model(store)
+ #cell = gtk.CellRendererText()
+ #self.combobox_output.pack_start(cell, True)
+ #self.combobox_output.add_attribute(cell, 'text', 0)
+
+ store = gtk.ListStore(gobject.TYPE_STRING)
+ self.combobox_category.set_model(store)
+ cell = gtk.CellRendererText()
+ self.combobox_category.pack_start(cell, True)
+ self.combobox_category.add_attribute(cell, 'text', 0)
+
+
count = 0
for value in self.core.display.values():
self.combobox_category.append_text(value)
- if self.core.display[client.category] == value: self.combobox_category.set_active(count)
+ if self.core.display[client.role] == value: self.combobox_category.set_active(count)
count = count + 1
self.client = client
count = 1
self.combobox_output.set_active(0 )
- for key in self.core.pa_output_descriptions.keys():
- self.combobox_output.append_text(self.core.pa_output_descriptions[key])
- if self.core.pa_outputs[key] == client.output:
+ for key in self.core.pa.sinks.keys():
+ self.combobox_output.append_text(self.core.pa.sinks[key].description)
+ if self.core.pa.sinks[key].name == client.prefer_sink:
self.combobox_output.set_active(count)
count = count + 1
@@ -103,7 +98,8 @@ class EarCandyClientProperties(GladeWindow):
def on_button_apply_clicked(self, widget):
self.client.description = self.entry_description.get_text()
- self.client.volume_default = int(self.hscale_max_volume.get_value())
+ self.client.volume_max = int(self.hscale_max_volume.get_value())
+ self.client.volume_min = int(self.hscale_min_volume.get_value())
self.client.rule_re_window_title = re.compile( self.entry_window_title.get_text() )
self.client.rule_re_command = re.compile( self.entry_command.get_text() )
self.client.rule_re_application = re.compile( self.entry_application.get_text() )
@@ -111,14 +107,16 @@ class EarCandyClientProperties(GladeWindow):
self.client.apply_volume_meter_hack = self.checkbutton_fix.get_active()
self.client.fade_volume = self.checkbutton_fade_volume.get_active()
- self.client.output = ""
- for key in self.core.pa_output_descriptions.keys():
- if self.core.pa_output_descriptions[key] == self.combobox_output.get_active_text():
- self.client.output = self.core.pa_outputs[key]
+ output = ""
+ self.client.prefer_sink = ""
+ for key in self.core.pa.sinks.keys():
+ if self.core.pa.sinks[key].description == self.combobox_output.get_active_text():
+ self.client.prefer_sink = self.core.pa.sinks[key].name
+ self.core.pa.move_client_to_sink(self.client)
- self.client.category = ""
+ self.client.role = ""
for key in self.core.display.keys():
if self.core.display[key] == self.combobox_category.get_active_text():
- self.client.category = key
+ self.client.role = key
self.stop()
diff --git a/ear_candy/EarCandyPrefs.py b/earcandy/ui/EarCandyPrefs.py
similarity index 56%
rename from ear_candy/EarCandyPrefs.py
rename to earcandy/ui/EarCandyPrefs.py
index 853f987..9b89867 100644
--- a/ear_candy/EarCandyPrefs.py
+++ b/earcandy/ui/EarCandyPrefs.py
@@ -19,24 +19,15 @@ import sys
import gobject
import re
import time
-try:
- import pygtk
- pygtk.require("2.0")
-except:
- pass
-try:
- import gtk
- import gtk.glade
-except:
- sys.exit(1)
-
-
-from Threads import threaded
+import gtk
+
+from earcandy.util.Threads import threaded
from glade_window import GladeWindow
from EarCandyAppSelect import EarCandyAppSelect
from EarCandyClientProperties import EarCandyClientProperties
+from Client import Client
-class EarCandayPref(GladeWindow):
+class EarCandayPrefs(GladeWindow):
def __init__(self, core):
self.core = core
@@ -45,29 +36,35 @@ class EarCandayPref(GladeWindow):
GladeWindow.__init__(self, "pulseoptions.glade", "dialog_options")
- self.treeview_pulse_clients = self.wtree.get_widget("treeview_pulse_clients")
- self.label_pulse_client = self.wtree.get_widget("label_pulse_client")
- self.entry_pulse_client_description = self.wtree.get_widget("entry_pulse_client_description")
- self.comboboxentry_window_match = self.wtree.get_widget("comboboxentry_window_match")
- self.radiobutton_do_not_mute_others = self.wtree.get_widget("radiobutton_do_not_mute_others")
- self.radiobutton_mute_others_onfocus = self.wtree.get_widget("radiobutton_mute_others_onfocus")
- self.radiobutton_mute_others_focus = self.wtree.get_widget("radiobutton_mute_others_focus")
- self.checkbutton_mute_onblur = self.wtree.get_widget("checkbutton_mute_onblur")
- self.checkbutton_do_not_mute = self.wtree.get_widget("checkbutton_do_not_mute")
- self.vscale_volume = self.wtree.get_widget("vscale_volume")
- self.vscale_mute = self.wtree.get_widget("vscale_mute")
- self.combobox_profile = self.wtree.get_widget("combobox_profile")
- self.entry_application = self.wtree.get_widget("entry_application")
- self.entry_command = self.wtree.get_widget("entry_command")
- self.entry_window_title = self.wtree.get_widget("entry_window_title")
- self.checkbutton_channel_window = self.wtree.get_widget("checkbutton_channel_window")
- self.label_client = self.wtree.get_widget("label_client")
- self.hscale_fade = self.wtree.get_widget("hscale_fade")
- self.hscale_mute_level = self.wtree.get_widget("hscale_mute_level")
- self.checkbutton_tray = self.wtree.get_widget("checkbutton_tray")
- self.checkbutton_output = self.wtree.get_widget("checkbutton_output")
- self.combobox_view = self.wtree.get_widget("combobox_view")
- self.checkbutton_autostart = self.wtree.get_widget("checkbutton_autostart")
+ self.treeview_pulse_clients = self.wtree.get_object("treeview_pulse_clients")
+ self.label_pulse_client = self.wtree.get_object("label_pulse_client")
+ self.entry_pulse_client_description = self.wtree.get_object("entry_pulse_client_description")
+ self.comboboxentry_window_match = self.wtree.get_object("comboboxentry_window_match")
+ self.radiobutton_do_not_mute_others = self.wtree.get_object("radiobutton_do_not_mute_others")
+ self.radiobutton_mute_others_onfocus = self.wtree.get_object("radiobutton_mute_others_onfocus")
+ self.radiobutton_mute_others_focus = self.wtree.get_object("radiobutton_mute_others_focus")
+ self.checkbutton_mute_onblur = self.wtree.get_object("checkbutton_mute_onblur")
+ self.checkbutton_do_not_mute = self.wtree.get_object("checkbutton_do_not_mute")
+ self.vscale_volume = self.wtree.get_object("vscale_volume")
+ self.vscale_mute = self.wtree.get_object("vscale_mute")
+ self.combobox_profile = self.wtree.get_object("combobox_profile")
+ self.entry_application = self.wtree.get_object("entry_application")
+ self.entry_command = self.wtree.get_object("entry_command")
+ self.entry_window_title = self.wtree.get_object("entry_window_title")
+ self.checkbutton_channel_window = self.wtree.get_object("checkbutton_channel_window")
+ self.label_client = self.wtree.get_object("label_client")
+ self.hscale_fade = self.wtree.get_object("hscale_fade")
+ self.hscale_mute_level = self.wtree.get_object("hscale_mute_level")
+ self.checkbutton_tray = self.wtree.get_object("checkbutton_tray")
+ self.checkbutton_output = self.wtree.get_object("checkbutton_output")
+ self.combobox_view = self.wtree.get_object("combobox_view")
+ self.checkbutton_autostart = self.wtree.get_object("checkbutton_autostart")
+ self.treeview_plugins = self.wtree.get_object("treeview_plugins")
+ self.checkbutton_mute_phone = self.wtree.get_object("checkbutton_mute_phone")
+ self.combobox_sink = self.wtree.get_object("combobox_sink")
+ self.vbox_repeater = self.wtree.get_object("vbox_repeater")
+ self.table_clients = self.wtree.get_object("table_clients")
+ self.treeview_outputs = self.wtree.get_object("treeview_outputs")
signals = {
"on_entry_pulse_client_description_changed" : self.on_entry_pulse_client_description_changed,
@@ -75,8 +72,8 @@ class EarCandayPref(GladeWindow):
"on_entry_application_changed" : self.on_entry_application_changed,
"on_entry_command_changed" : self.on_entry_command_changed,
"on_entry_window_title_changed" : self.on_entry_window_title_changed,
- "on_vscale_volume_change_value" : self.on_vscale_volume_change_value,
- "on_vscale_mute_change_value" : self.on_vscale_mute_change_value,
+ #"on_vscale_volume_change_value" : self.on_vscale_volume_change_value,
+ #"on_vscale_mute_change_value" : self.on_vscale_mute_change_value,
"on_checkbutton_channel_window_toggled" : self.on_checkbutton_channel_window_toggled,
"on_treeview_pulse_clients_cursor_changed" : self.on_treeview_pulse_clients_cursor_changed,
@@ -99,81 +96,90 @@ class EarCandayPref(GladeWindow):
"on_button_reset_clicked" : self.on_button_reset_clicked,
- "on_checkbutton_autostart_toggled" : self.on_checkbutton_autostart_toggled
- }
- self.wtree.signal_autoconnect(signals)
-
- # Setup tree
+ "on_checkbutton_autostart_toggled" : self.on_checkbutton_autostart_toggled,
+ "on_checkbutton_mute_phone_toggled" : self.on_checkbutton_mute_phone_toggled,
- column = gtk.TreeViewColumn((''))
- column.set_spacing(4)
- cell = gtk.CellRendererPixbuf()
- column.pack_start(cell, False)
- column.set_attributes(cell, pixbuf=0)
- self.treeview_pulse_clients.append_column(column)
+ "on_combobox_sink_changed" : self.on_combobox_sink_changed,
- column_label = gtk.TreeViewColumn(('Application'))
- cell = gtk.CellRendererText()
- column_label.pack_start(cell, True)
- column_label.set_attributes(cell, markup=1)
- self.treeview_pulse_clients.append_column(column_label)
- column_label.set_expand(True)
+ "on_treeview_outputs_drag_drop" : self.on_treeview_outputs_drag_drop
+ }
+ self.wtree.connect_signals(signals)
#list store for cell renderer
self.cb_model = gtk.ListStore(str)
for key in self.core.display.keys():
self.cb_model.append([self.core.display[key]])
- cell = gtk.CellRendererCombo()
- cell.connect('edited', self.on_client_category_changed)
- column = gtk.TreeViewColumn('Category', cell)
- cell.set_property("model",self.cb_model)
- cell.set_property('text-column', 0)
- cell.set_property('editable', True)
- cell.connect('editing-started', self.cell_edit_start)
- column.set_attributes(cell, text = 2)
- column.set_expand(False)
- self.treeview_pulse_clients.append_column(column)
-
- """column = gtk.TreeViewColumn('Fix')
- cell = gtk.CellRendererToggle()
- cell.set_property('activatable', True)
- cell.connect( 'toggled', self.on_client_fade_toggled)
- column.pack_start(cell, False)
- column.set_attributes(cell, active=3)
- self.treeview_pulse_clients.append_column(column)"""
-
self.last_popup = None
self.last_path = None
#self.update_treeview()
- self.combobox_view.set_active(0)
self.checkbutton_autostart.set_active( self.core.is_auto_start() )
+ self.checkbutton_mute_phone.set_active( self.core.mute_phone )
+
+
+ column_label = gtk.TreeViewColumn(('Source'))
+ cell = gtk.CellRendererText()
+ column_label.pack_start(cell, True)
+ column_label.set_attributes(cell, markup=1)
+ self.treeview_outputs.append_column(column_label)
+ column_label.set_expand(True)
+ self.store = gtk.ListStore(str, str, object)
+ self.treeview_outputs.set_model(self.store)
+
+ self.clients = {}
+
+ # fix color : Fix this for dark themes....
+ style = gtk.Style()
+ viewport1 = self.wtree.get_object("viewport1")
+ viewport1.modify_bg(gtk.STATE_NORMAL, style.base[gtk.STATE_NORMAL])
+
+
+ def on_treeview_outputs_drag_drop(self, widget, b):
+ self.update_ouput_order()
+
+
+ def on_combobox_sink_changed(self, widget):
+
+ return
+
+ def on_treeview_plugins_toggled(self, cell, path):
+ model = self.treeview_plugins.get_model()
+ iter = model.get_iter((int(path),))
+ if not iter: return
+
+ model[path][0] = not model[path][0]
+ model[path][2].enabled = model[path][0]
+
+ def on_checkbutton_mute_phone_toggled(self, widget):
+ self.core.mute_phone = not self.core.mute_phone
def on_checkbutton_autostart_toggled(self, widget):
self.core.set_auto_start( widget.get_active() )
def on_button_reset_clicked(self, widget):
- md = gtk.MessageDialog(self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_YES_NO, message_format="Are you sure you want to reset all volume levels?")
+ md = gtk.MessageDialog(self.window, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_YES_NO, message_format="Are you sure you want to reset all your settings?")
result = md.run()
md.destroy()
if result == gtk.RESPONSE_YES:
- self.core.reset_all_volumes(False)
+ self.window.destroy()
+ self.core.reset_all()
+ self.core.pref = None
+ self.core.open_preferances()
def cell_edit_start(self, editable, control, path):
self.last_popup = control
self.last_path = path
def on_combobox_view_changed(self, widget):
- self.update_treeview()
+ return
+ #self.update_treeview()
def on_checkbutton_tray_toggled(self, widget):
- self.core.tray.set_visible( widget.get_active() )
- if widget.get_active():
- self.core.move_all_sinks()
+ self.core.status_icon.set_visible( widget.get_active() )
def on_checkbutton_output_toggled(self, widget):
self.core.follow_new_outputs = widget.get_active()
@@ -195,14 +201,14 @@ class EarCandayPref(GladeWindow):
def on_hscale_fade_value_changed(self, widget):
self.core.fade_timer_speed = self.hscale_fade.get_value()
- def on_client_category_changed(self, cell, row, newText):
+ def on_client_role_changed(self, cell, row, newText):
model, iter = self.treeview_pulse_clients.get_selection().get_selected()
if not iter: return
self.selected_client = model.get_value(iter, 5)
for key in self.core.display.keys():
if self.core.display[key] == newText:
- self.selected_client.category = key
+ self.selected_client.role = key
self.store.set(iter,
2, newText
)
@@ -216,7 +222,7 @@ class EarCandayPref(GladeWindow):
result = md.run()
md.destroy()
if result == gtk.RESPONSE_YES:
- self.selected_client.category = ""
+ self.selected_client.role = ""
self.selected_client.iter = None
self.store.remove(iter)
self.core.save()
@@ -226,11 +232,14 @@ class EarCandayPref(GladeWindow):
model, iter = self.treeview_pulse_clients.get_selection().get_selected()
if not iter: return
self.selected_client = model.get_value(iter, 5)
- print self.selected_client
- eccp = EarCandyClientProperties(self.core, self.selected_client, self.window)
+
+
+
+ def open_client_properties(self, client):
+ eccp = EarCandyClientProperties(self.core, client, self.window)
eccp.run()
- self.update_client(self.selected_client)
+ self.update_client(client)
self.core.save()
def on_button_add_new_clicked(self, widget):
@@ -261,7 +270,7 @@ class EarCandayPref(GladeWindow):
def on_combobox_profile_changed(self, widget):
for key in self.vals.keys():
if self.vals[key] == self.combobox_profile.get_active():
- self.current_client.category = key
+ self.current_client.role = key
self.core.save()
def on_vscale_volume_change_value(self, widget, a, b):
@@ -306,68 +315,120 @@ class EarCandayPref(GladeWindow):
def __get_title(self, client):
text = client.name
- if client.description:
- text = client.description
- return text
+ if client.is_active() and client.get_status():
+ text = "<b>" + client.description + "</b>"
+ for sink in client.sinks.values():
+ text += "\n<small><i>" + sink.name + "</i></small>"
+ return text
+
+ elif not client.is_active():
+ return "<i>" + client.description + "</i>"
+
+ return client.description
def update_treeview(self):
- for client in self.core.pa_clients.values():
+ for client in self.core.pa.clients.values():
client.iter = None
- self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str, bool, int, object)
+ self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, str, bool, int, object, str)
self.treeview_pulse_clients.set_model(self.store)
icon_theme = gtk.icon_theme_get_default()
icon = icon_theme.lookup_icon("audio-volume-medium", 32, 0).load_icon()
- for client in self.core.pa_clients.values():
- if client.category and not (client.name in self.core.ignore):
+ for client in self.core.pa.clients.values():
+ if not (client.name in self.core.ignore):
if client.is_active() or self.combobox_view.get_active() == 1:
- client.iter = self.store.append(([client.icon or icon, self.__get_title(client), self.core.display[client.category], client.apply_volume_meter_hack, 50, client]))
+ client.iter = self.store.append(([client.icon or icon, self.__get_title(client), self.core.display[client.role], client.apply_volume_meter_hack, 0, client, client.description]))
- self.store.set_sort_column_id(1, gtk.SORT_ASCENDING)
+ self.store.set_sort_column_id(6, gtk.SORT_ASCENDING)
def update_client(self, client):
# load saved icon
- if not client.icon and client.icon_name:
- print "load client icon", client.icon_name
- icon_theme = gtk.icon_theme_get_default()
- try:
- client.icon = icon_theme.load_icon(client.icon_name, 32, 0)
- except:
- client.icon = icon_theme.load_icon("audio-volume-medium", 32, 0)
-
- if client.iter:
- if client.is_active():
- self.store.set(client.iter,
- 0, client.icon,
- 1, self.__get_title(client),
- 4, client.get_volume()
- )
- if client.category in self.core.display.keys():
- self.store.set(client.iter,
- 2, self.core.display[client.category]
- )
- else:
- pass
- # This needs to be on a timer
- #self.store.remove(client.iter)
- #client.iter = None
- else:
- if client.has_rule() and not client.name in self.core.ignore:
+ if not client.icon:
+ if client.icon_name:
+ print "load client icon", client.icon_name
icon_theme = gtk.icon_theme_get_default()
- icon = icon_theme.lookup_icon("audio-volume-medium", 32, 0).load_icon()
- client.iter = self.store.append(([client.icon or icon, self.__get_title(client), self.core.display[client.category], client.apply_volume_meter_hack, client.get_volume(), client]))
+ try:
+ client.icon = icon_theme.load_icon(client.icon_name, 32, 0)
+ except:
+ client.icon = icon_theme.load_icon("audio-volume-medium", 32, 0)
+ elif client.application and client.application.icon_name:
+ icon_theme = gtk.icon_theme_get_default()
+ try:
+ client.icon = icon_theme.load_icon(client.application.icon_name, 32, 0)
+ except:
+ client.icon = icon_theme.load_icon("audio-volume-medium", 32, 0)
+
+ if not self.clients.has_key(client.name):
+
+ client_gtk = Client(client, self.core)
+ self.clients[client.name] = client_gtk
+ self.vbox_repeater.pack_start(client_gtk.gtk, expand=False, fill=False, padding=0)
+ client_gtk.gtk.show()
+
+ count = 0
+ for key in self.core.display.keys():
+ if key == client.role:
+ client_gtk.gtk_role_combobox.set_active(count)
+ break
+ count = count + 1
+
+ else:
+ client_gtk = self.clients[client.name]
+
+ client_gtk.set_status(client.is_active())
- self.store.set_sort_column_id(1,gtk.SORT_ASCENDING)
+ client_gtk.gtk_progressbar_meter.set_fraction(client.get_volume() / 100)
+ client_gtk.gtk_label.set_markup(self.__get_title(client))
+ if client.icon: client_gtk.gtk_icon.set_from_pixbuf(client.icon)
+
+ for key in self.core.display.keys():
+ self.cb_model.append([self.core.display[key]])
+
+ client_gtk.gtk.show()
+
+ def update_output(self, output, priority):
+ itr = self.store.get_iter_first()
+ while itr:
+ o = self.store.get_value(itr, 2)
+ if o.name == output.name:
+ if not output.index:
+ self.store.remove(itr)
+ self.update_ouput_order()
+ return
+ itr = self.store.iter_next(itr)
+
+ self.store.insert(priority, ([output.name, output.description, output]))
+
+ self.update_ouput_order()
+
+ def update_ouput_order(self):
+
+ itr = self.store.get_iter_first()
+ count = 1
+ flagCurrent = False
+ while itr:
+ output = self.store.get_value(itr, 2)
+ output.priority = count
+ if count == 1:
+ self.store.set_value(itr, 1, "<b>" + output.description + "</b>")
+ else:
+ self.store.set_value(itr, 1, output.description)
+
+ itr = self.store.iter_next(itr)
+
+ count = count + 1
+
+ self.core.pa.set_default_prefered_device()
def run(self):
self.window.show()
self.hscale_mute_level.set_value(self.core.mute_level)
self.hscale_fade.set_value(self.core.fade_timer_speed)
- self.checkbutton_tray.set_active( self.core.tray.get_visible() )
- self.checkbutton_output.set_active( self.core.follow_new_outputs )
+ self.checkbutton_tray.set_active( self.core.status_icon.get_visible() )
self.window.present()
+ self.core.pa.pa_context_get_sink_info_list()
return self.return_value
def stop(self):
@@ -375,18 +436,6 @@ class EarCandayPref(GladeWindow):
self.core.close_preferances()
self.window.destroy()
- @threaded
- def remove_client_on_timer(self, client):
- #time.sleep(60)
- #gobject.idle_add(self.remove_client, client)
- return
-
- def remove_client(self, client):
- if not client.is_active() and client.iter :
- self.store.remove(client.iter)
- client.iter = None
-
-
diff --git a/ear_candy/TrayIcon.py b/earcandy/ui/TrayIcon.py
similarity index 66%
rename from ear_candy/TrayIcon.py
rename to earcandy/ui/TrayIcon.py
index 44a8868..5d89f36 100644
--- a/ear_candy/TrayIcon.py
+++ b/earcandy/ui/TrayIcon.py
@@ -19,16 +19,7 @@ import os
import sys
import gtk
-def find_program_file(path):
- """Finds a program file, for example, a png included with the program.
- First looks for it in files/ under the parent directory of the parent directory
- of ear_candy.py
- Then looks for it in /usr/share/earcandy
- Returns the path of the file"""
- if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "files",path)):
- return os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "files",path)
- else:
- return os.path.join(sys.prefix, "share/earcandy", path)
+from earcandy.earcandyconfig import getdatapath
class EarCandyStatusIcon(gtk.StatusIcon):
def __init__(self, core):
@@ -39,6 +30,7 @@ class EarCandyStatusIcon(gtk.StatusIcon):
<ui>
<menubar name="Menubar">
<menu action="Menu">
+ <separator/>
<menuitem action="Preferences"/>
<menuitem action="About"/>
<separator/>
@@ -49,7 +41,10 @@ class EarCandyStatusIcon(gtk.StatusIcon):
'''
actions = [
('Menu', None, 'Menu'),
+ ('Mute', "stock_volume-mute", '_Mute...', None, 'Mute sound', self.on_preferences),
+ ('Lock', "stock_lock", '_Lock...', None, 'Lock volume levels', self.on_preferences),
('Preferences', gtk.STOCK_PREFERENCES, '_Preferences...', None, 'Edit Preferences', self.on_preferences),
+
('About', gtk.STOCK_ABOUT, '_About...', None, 'About Ear Candy', self.on_about),
('Exit', gtk.STOCK_STOP, '_Exit...', None, 'Exit Ear Candy', self.stop)]
ag = gtk.ActionGroup('Actions')
@@ -65,38 +60,31 @@ class EarCandyStatusIcon(gtk.StatusIcon):
self.connect('activate', self.on_activate)
self.connect('popup-menu', self.on_popup_menu)
+
def on_activate(self, data):
- if not self.core.slider.active:
- self.core.slider.window.set_position(gtk.WIN_POS_MOUSE)
- screen, rect, orin = self.get_geometry()
- x, y = self.core.slider.window.get_position()
- if rect.y - rect.height/2 > y - rect.height:
- self.core.slider.window.move(x, y + rect.height)
- self.core.slider.run()
+ self.core.open_preferances()
def on_preferences(self, widget):
self.core.open_preferances()
- def on_popup_menu(self, status, button, time):
+ def on_reset(self, widget):
+ self.core.reset()
+
+ def on_popup_menu(self, status, button=None, time=0):
self.menu.popup(None, None, None, button, time)
def on_about(self, data):
dialog = gtk.AboutDialog()
dialog.set_name('Ear Candy')
dialog.set_version(str(self.core.version))
- dialog.set_comments('An pulseaudio / X.org sound manager')
- dialog.set_website('http://edge.launchpad.net/eyecandy')
+ dialog.set_comments('An pulseaudio / X.org sound volume manager')
+ dialog.set_website('https://launchpad.net/earcandy')
dialog.run()
dialog.destroy()
-
def set_icon(self):
- if self.core.active:
- icon_path = find_program_file("earsLabel.png")
- else:
- icon_path = find_program_file("earsLabelOff.png")
- self.set_from_file( icon_path )
+ self.set_from_file( os.path.join(getdatapath(), 'media', 'icon.png') )
def stop(self, data=None):
self.core.exit()
diff --git a/ear_candy/VolumeSlider.py b/earcandy/ui/VolumeSlider.py
similarity index 84%
rename from ear_candy/VolumeSlider.py
rename to earcandy/ui/VolumeSlider.py
index 05df249..8579a82 100644
--- a/ear_candy/VolumeSlider.py
+++ b/earcandy/ui/VolumeSlider.py
@@ -15,23 +15,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys, os
-try:
- import pygtk
- pygtk.require("2.0")
-except:
- pass
-try:
- import gtk
- import gtk.glade
-except:
- sys.exit(1)
+
+import gtk
+import gtk.glade
import re
import copy
from glade_window import GladeWindow
-from window.WindowWatcher import WindowWatcher
-from Client import Client
class EarCandyVolumeSlider(GladeWindow):
def __init__(self, core):
@@ -41,12 +32,13 @@ class EarCandyVolumeSlider(GladeWindow):
self.vscale_volume = self.wtree.get_widget("vscale_volume")
self.image_status = self.wtree.get_widget("image_status")
+ self.image_mute = self.wtree.get_widget("image_mute")
signals = {
"on_popup_volume_control_focus_out_event" : self.on_popup_volume_control_focus_out_event,
"on_vscale_volume_change_value" : self.on_vscale_volume_change_value,
- "on_button_suspend_clicked" : self.on_button_suspend_clicked
-
+ "on_button_suspend_clicked" : self.on_button_suspend_clicked,
+ "on_button_mute_clicked" : self.on_button_mute_clicked
}
self.wtree.signal_autoconnect(signals)
@@ -58,11 +50,21 @@ class EarCandyVolumeSlider(GladeWindow):
# self.combobox_client_category.append_text( val )
self.active = False
+ def on_button_mute_clicked(self, widget):
+ self.core.set_mute( not self.core.is_mute )
+ self.stop()
+
def on_button_suspend_clicked(self, widget):
self.core.set_active( not self.core.active )
self.stop()
+ def update_mute_status(self):
+ if self.core.is_mute:
+ self.image_mute.set_from_file( self.core.find_program_file("volume-magic.png") )
+ else:
+ self.image_mute.set_from_file( self.core.find_program_file("volume-muted.png") )
+
def update_active_status(self):
if self.core.active:
self.image_status.set_from_stock("gtk-media-pause", 4)
@@ -85,7 +87,7 @@ class EarCandyVolumeSlider(GladeWindow):
def run(self):
self.active = True
- self.core.get_current_sink_volume()
+ #self.core.get_current_sink_volume()
self.window.present()
gtk.gdk.keyboard_grab(self.window.window, True)
gtk.gdk.pointer_grab(self.window.window, True,
diff --git a/ear_candy/pulseaudio/__init__.py b/earcandy/ui/__init__.py
similarity index 100%
copy from ear_candy/pulseaudio/__init__.py
copy to earcandy/ui/__init__.py
diff --git a/ear_candy/glade_window.py b/earcandy/ui/glade_window.py
similarity index 59%
rename from ear_candy/glade_window.py
rename to earcandy/ui/glade_window.py
index 374a283..691e9b2 100644
--- a/ear_candy/glade_window.py
+++ b/earcandy/ui/glade_window.py
@@ -15,38 +15,25 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
-try:
- import pygtk
- pygtk.require("2.0")
-except:
- pass
-try:
- import gtk
- import gtk.glade
-except:
- sys.exit(1)
-
+import gtk
import os, sys
+from earcandy.earcandyconfig import getdatapath
-def find_program_file(path):
- """Finds a program file, for example, a png included with the program.
- First looks for it in files/ under the parent directory of the parent directory
- of ear_candy.py
- Then looks for it in /usr/share/earcandy
- Returns the path of the file"""
- if os.path.exists(os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "files",path)):
- return os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "files",path)
- else:
- return os.path.join(sys.prefix, "share/earcandy", path)
-
class GladeWindow():
def __init__(self, glade_file, window_name, parent=None):
self.return_value = None
self.parent = parent
- self.wtree = gtk.glade.XML(find_program_file(glade_file))
- self.window = self.wtree.get_widget(window_name)
+
+ ui_filename = os.path.join(getdatapath(), 'ui', 'EarCandy.ui')
+ if not os.path.exists(ui_filename):
+ ui_filename = None
+
+ self.wtree = gtk.Builder()
+ self.wtree.add_from_file(ui_filename)
+
+ self.window = self.wtree.get_object(window_name)
self.window.connect("destroy", self.on_destroy)
if parent: self.window.set_transient_for(parent)
diff --git a/ear_candy/DesktopFiles.py b/earcandy/util/DesktopFiles.py
similarity index 100%
rename from ear_candy/DesktopFiles.py
rename to earcandy/util/DesktopFiles.py
diff --git a/ear_candy/Freedesktop.py b/earcandy/util/Freedesktop.py
similarity index 100%
rename from ear_candy/Freedesktop.py
rename to earcandy/util/Freedesktop.py
diff --git a/ear_candy/Threads.py b/earcandy/util/Threads.py
similarity index 100%
rename from ear_candy/Threads.py
rename to earcandy/util/Threads.py
diff --git a/ear_candy/pulseaudio/__init__.py b/earcandy/util/__init__.py
similarity index 100%
copy from ear_candy/pulseaudio/__init__.py
copy to earcandy/util/__init__.py
diff --git a/earcandy/windows/Application.py b/earcandy/windows/Application.py
new file mode 100644
index 0000000..49b8db4
--- /dev/null
+++ b/earcandy/windows/Application.py
@@ -0,0 +1,69 @@
+
+import os
+import gtk
+from earcandy.util.DesktopFiles import DesktopFiles
+import logging
+
+log = logging.getLogger('WindowApplication')
+log.setLevel(logging.WARNING)
+
+class Application():
+ def __init__(self, pid, command, name, desktop_files, icon):
+ self.command = os.path.basename(command.lower().strip())
+ self.name = name.lower().strip()
+ self.role = ""
+ self.icon = icon
+ self.icon_name = ""
+ self.description = name
+
+ # current application window info
+ self.pid = pid
+ self.window_name = ""
+ self.x = 0
+ self.y = 0
+ self.fullscreen = False
+
+ self.__match_desktop(desktop_files)
+ return
+
+ def __match_desktop(self, desktop_files):
+
+ #print "checking desktop files for match....", self.name
+ # Try looking up the application list
+ for a in desktop_files:
+ ex = a.get_exec_array()[0].lower()
+
+ # Check for pulse audio settings first...
+ if a.get("X-PulseAudio-Properties"):
+ self.role = a.get("X-PulseAudio-Properties")
+ break
+
+ if a.get("Categories") and len(a.get("Categories")) == 1 and a.get("Categories")[0] == "Core":
+ # skip entries that are core only
+ pass
+ else:
+ if a.get("X-GNOME-Bugzilla-Product") == self.name or ex == self.name or ex == self.command or a.get("Name").lower() == self.name or a.get("Name").lower() == self.command or ex + ".real" == self.command :
+ self.icon_name = a.get("Icon")
+ self.description = a.get("Name")
+ self.role = self.__desktop_categories_to_role( a.get("Categories") )
+ log.debug("WW :: desktop file : %s" % a.filename)
+ break
+
+ if self.command == "skype.real":
+ self.role = "phone"
+
+ log.debug("WW :: role : %s" % self.role)
+
+ def __desktop_categories_to_role(self, categories):
+ if categories:
+ for role in categories:
+ if "Telephony" in categories or "InstantMessaging" in categories:
+ return "phone"
+ if "Music" in categories:
+ return "music"
+ if "Video" in categories:
+ return "video"
+ if "AudioVideo" in categories:
+ return "music"
+
+ return ""
diff --git a/earcandy/windows/Watcher.py b/earcandy/windows/Watcher.py
new file mode 100644
index 0000000..49b563e
--- /dev/null
+++ b/earcandy/windows/Watcher.py
@@ -0,0 +1,112 @@
+import wnck
+import gobject
+import logging
+from earcandy.util.DesktopFiles import DesktopFiles
+from Application import Application
+
+log = logging.getLogger('WindowWatcher')
+log.setLevel(logging.WARNING)
+
+class Watcher():
+
+ def __init__(self):
+ self.screen = wnck.screen_get_default()
+ self.screen.connect("active_window_changed", self.active_window_changed)
+ self.screen.connect("window_opened", self.window_opened)
+
+ self.desktop_files = DesktopFiles().read_all()
+ self.current_window = None
+ self.applications = {}
+
+ self.callback = None
+
+
+
+
+ def check_all(self):
+ for win in self.screen.get_windows():
+ self.update("exists", win)
+
+ def get_command(self, pid):
+ command = str(pid)
+ try:
+ # Try and get command from PID
+ f = open("/proc/%s/cmdline" % pid, "r")
+ command = f.readline()[:-1]
+ f.close()
+ except:
+ pass
+ return command
+
+ def window_opened(self, screen, win):
+ if win and win.get_window_type() != wnck.WINDOW_DOCK: # ignore docks
+
+ app = win.get_application ()
+ app_name = app.get_name()
+
+ pid = win.get_pid()
+ command = self.get_command(pid)
+ category = ""
+
+ if not self.applications.has_key(command):
+ a = Application(pid, command, app_name, self.desktop_files, win.get_icon ())
+ self.applications[command] = a
+
+ log.debug("WW :: window opened : %s" % app_name)
+ self.update("open", win)
+ #win.connect("geometry-changed", self.geometry_changed)
+
+ def active_window_changed(self, screen, old_window):
+ #print screen
+ win = screen.get_active_window ()
+ if win:
+ log.debug("WW :: active window changed : %s" % win.get_name())
+ self.update("active", win)
+
+
+ def update(self, state, win):
+ if win and win.get_window_type() != wnck.WINDOW_DOCK: # ignore docks
+
+ #try:
+ app = win.get_application ()
+ app_name = app.get_name()
+ pid = win.get_pid()
+
+ command = self.get_command(pid)
+
+ application = None
+ application = self.applications[command]
+ application.pid = pid
+
+ geom = win.get_geometry()
+ x = float(geom[1])
+ y = float(geom[0])
+ w = float(geom[2])
+ h = float(geom[3])
+
+ if win.get_name():
+ application.window_name = win.get_name()
+
+ #application.x = x+h/2
+ #application.y = y+w/2
+ #application.fullscreen = win.is_fullscreen () or win.is_maximized ()
+ #application.icon = win.get_icon ()
+ log.debug("WW :: application : %s" % application.description)
+
+ if self.callback:
+ gobject.idle_add(self.callback, application, state)
+ #except:
+ # print "error reading window", win
+
+ def geometry_changed(self, win):
+ self.update("geo", win)
+
+
+if __name__ == '__main__':
+ logging.basicConfig(level=log.debug)
+ import gtk
+ # Turn on gtk threading
+ gtk.gdk.threads_init()
+ ww = Watcher()
+ gtk.main()
+
diff --git a/ear_candy/pulseaudio/__init__.py b/earcandy/windows/__init__.py
similarity index 100%
rename from ear_candy/pulseaudio/__init__.py
rename to earcandy/windows/__init__.py
diff --git a/files/earcandy.desktop b/files/earcandy.desktop
deleted file mode 100644
index 89c8497..0000000
--- a/files/earcandy.desktop
+++ /dev/null
@@ -1,9 +0,0 @@
-[Desktop Entry]
-Name=Ear Candy
-Comment=Gracefully fade applications volume in and out
-Terminal=false
-Icon=earsLabel
-Type=Application
-Exec=earcandy
-Categories=Utility;
-StartupNotify=true
diff --git a/files/earsLabel.png b/files/earsLabel.png
deleted file mode 100644
index b8ecce8..0000000
Binary files a/files/earsLabel.png and /dev/null differ
diff --git a/files/earsLabelOff.png b/files/earsLabelOff.png
deleted file mode 100644
index 2395f7b..0000000
Binary files a/files/earsLabelOff.png and /dev/null differ
diff --git a/po/earcandy.pot b/po/earcandy.pot
new file mode 100644
index 0000000..4434017
--- /dev/null
+++ b/po/earcandy.pot
@@ -0,0 +1,219 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL at ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-04-15 22:38+1200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL at ADDRESS>\n"
+"Language-Team: LANGUAGE <LL at li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../data/ui/EarcandyWindow.ui.h:1
+msgid "Earcandy"
+msgstr ""
+
+#: ../data/ui/EarcandyWindow.ui.h:2
+msgid "Status Area"
+msgstr ""
+
+#: ../data/ui/EarcandyWindow.ui.h:3
+msgid ""
+"Your application has been created!\n"
+"\n"
+"To start changing this user interface, run 'quickly glade', which will open "
+"Glade so you can edit the default windows and dialogs.\n"
+"\n"
+"To change the behavior and edit the python code, run 'quickly edit', which "
+"will bring up a text editor."
+msgstr ""
+
+#: ../data/ui/EarcandyWindow.ui.h:8
+msgid "_Edit"
+msgstr ""
+
+#: ../data/ui/EarcandyWindow.ui.h:9
+msgid "_File"
+msgstr ""
+
+#: ../data/ui/EarcandyWindow.ui.h:10
+msgid "_Help"
+msgstr ""
+
+#: ../data/ui/EarcandyWindow.ui.h:11
+msgid "_View"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:1
+msgid " Output Devices "
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:2
+msgid "<b>1) Select pulseaudio client</b>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:3
+msgid "<b>2) Select application</b>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:4
+msgid "<b>3) Select category</b>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:5
+msgid ""
+"<b>Do not edit these values\n"
+" unless you know what you are doing</b>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:7
+msgid "<big>Banshee</big>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:8
+msgid ""
+"<i>For a client to show here it must have played a sound at least once</i>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:9
+msgid "<i>For an application to show here it must be open</i>"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:10
+msgid "Activity"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:11
+msgid "Advanced"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:12
+msgid "Advanced client matching rules"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:13
+msgid "Application"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:14
+msgid "Application control plugins"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:15
+msgid "Applications"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:16
+msgid "Autostart"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:17
+msgid "Category"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:18
+msgid "Client Name"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:19
+msgid "Command"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:20
+msgid "Cross Fade Speed"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:21
+msgid "Description"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:22
+msgid "Ear Candy - Audio Clients"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:23
+msgid "Edit"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:24
+msgid "Fade"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:25
+msgid "Fade volume"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:26
+msgid "Include 'phone' category when muting"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:27
+msgid "Lock volume levels"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:28
+msgid "Max"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:29
+msgid "Min"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:30
+msgid "Mute"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:31
+msgid "Mute sound"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:32
+msgid "Notification Tray"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:33
+msgid "Output Device"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:34
+msgid "Partial Mute Level"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:35
+msgid "Restore defaults"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:36
+msgid "Select PulseAudio Stream"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:37
+msgid "Show Icon"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:38
+msgid "Start on login"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:39
+msgid "Use volume meter detection"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:40
+msgid "Window Title"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:41
+msgid "[ automatic ]"
+msgstr ""
+
+#: ../data/ui/EarCandy.ui.h:42
+msgid "label"
+msgstr ""
diff --git a/setup.py b/setup.py
index f6a61a2..81fdc9b 100644
--- a/setup.py
+++ b/setup.py
@@ -1,47 +1,94 @@
#!/usr/bin/env python
-# author: David D Lowe
-# year: 2009
-# this file has been released into the public domain
-
-from distutils.core import setup
-import os, sys
-import glob
-
-
-def main():
-
- data_files = glob.glob("files/*") # data_files a list of strings
- # of the relative paths of all non-py files that should be included
- # ex: data_files is ['files/pulseoptions.glade', 'settings.xml', ...]
-
- setup( name="earcandy", # this should start with a lowercase letter
- #so that it can be used as a debian package name later on
- version="0.4", # string, version of your program, not python version
- description="A sound level manager that nicely fades applications in and out based on their profile and window focus", # short
- author="KillerKiwi",
- author_email="killerkiwi2005 at gmail.com",
- url="https://launchpad.net/earcandy", # home page for end-users
- license="unknown",
- packages=["ear_candy", "ear_candy.pulseaudio", "ear_candy.window"], # python packages, not debian packages
- data_files=[('share/earcandy', data_files),
- ('/usr/share/applications', ['files/earcandy.desktop']),
- ('/usr/share/pixmaps', ['files/earsLabel.png'])], # data_files is a list of tuples
- # each tuple contaning an installation path and a list of data files
- scripts=["runner/ear_candy"], # the script that should be run and installed in /usr/bin
- classifiers=["Development Status :: 5 - Production/Stable", "Intended Audience :: End Users/Desktop", "License :: unknown", "Operating System :: POSIX :: Linux"],
- # a bunch of optional tags, a list of classifiers can be found at http://pypi.python.org/pypi?:action=list_classifiers
- long_description="""A sound level manager that nicely fades applications in and out based on there profile and window focus
-
-What works now :
- - All volume adjustments are fades
- - Fade out music/video players on skype call
- - Fade to music player with focus when more than one
- - Fade out music player when video playing
- - Push sound to USB headsets on plugin
- - Categories to assign to clients
- - Sniffs desktop files to guess application category ... music/video/VoIP
- - Simplified pref UI for creating rules
- - Volume sniffing to fix youtube video issue""")
-
-if __name__ == "__main__":
- main()
+# -*- coding: utf-8 -*-
+### BEGIN LICENSE
+# This file is in the public domain
+### END LICENSE
+
+###################### DO NOT TOUCH THIS (HEAD TO THE SECOND PART) ######################
+
+try:
+ import DistUtilsExtra.auto
+except ImportError:
+ import sys
+ print >> sys.stderr, 'To build earcandy you need https://launchpad.net/python-distutils-extra'
+ sys.exit(1)
+
+assert DistUtilsExtra.auto.__version__ >= '2.10', 'needs DistUtilsExtra.auto >= 2.10'
+import os
+
+
+def update_data_path(prefix, oldvalue=None):
+
+ try:
+ fin = file('earcandy/earcandyconfig.py', 'r')
+ fout = file(fin.name + '.new', 'w')
+
+ for line in fin:
+ fields = line.split(' = ') # Separate variable from value
+ if fields[0] == '__earcandy_data_directory__':
+ # update to prefix, store oldvalue
+ if not oldvalue:
+ oldvalue = fields[1]
+ line = "%s = '%s'\n" % (fields[0], prefix)
+ else: # restore oldvalue
+ line = "%s = %s" % (fields[0], oldvalue)
+ fout.write(line)
+
+ fout.flush()
+ fout.close()
+ fin.close()
+ os.rename(fout.name, fin.name)
+ except (OSError, IOError), e:
+ print ("ERROR: Can't find earcandy/earcandyconfig.py")
+ sys.exit(1)
+ return oldvalue
+
+
+def update_desktop_file(datadir):
+
+ try:
+ fin = file('earcandy.desktop.in', 'r')
+ fout = file(fin.name + '.new', 'w')
+
+ for line in fin:
+ if 'Icon=' in line:
+ line = "Icon=%s\n" % (datadir + 'media/icon.png')
+ fout.write(line)
+ fout.flush()
+ fout.close()
+ fin.close()
+ os.rename(fout.name, fin.name)
+ except (OSError, IOError), e:
+ print ("ERROR: Can't find earcandy.desktop.in")
+ sys.exit(1)
+
+
+class InstallAndUpdateDataDirectory(DistUtilsExtra.auto.install_auto):
+ def run(self):
+ if self.root or self.home:
+ print "WARNING: You don't use a standard --prefix installation, take care that you eventually " \
+ "need to update quickly/quicklyconfig.py file to adjust __quickly_data_directory__. You can " \
+ "ignore this warning if you are packaging and uses --prefix."
+ previous_value = update_data_path(self.prefix + '/share/earcandy/')
+ update_desktop_file(self.prefix + '/share/earcandy/')
+ DistUtilsExtra.auto.install_auto.run(self)
+ update_data_path(self.prefix, previous_value)
+
+
+
+##################################################################################
+###################### YOU SHOULD MODIFY ONLY WHAT IS BELOW ######################
+##################################################################################
+
+DistUtilsExtra.auto.setup(
+ name='earcandy',
+ version='0.9',
+ license='GPL v2',
+ author='Jason Taylor',
+ author_email='killerkiwi2005 at gmail.com',
+ description='Automatic pulseaudio volume control',
+ long_description='A sound level manager that nicely fades applications in and out based on their media role and window focus',
+ url='https://launchpad.net/earcandy',
+ cmdclass={'install': InstallAndUpdateDataDirectory}
+ )
+
--
earcandy packaging
More information about the pkg-multimedia-commits
mailing list