My attempt to make a volume control context menu that launches from tint2.
Uses amixer
It wasn't showing properly untill I've installed libgtk-3-0
Here is the launcher =>
/usr/local/bin/tint2launchers/07-sound.desktop
[Desktop Entry]
Type=Application
Encoding=UTF-8
Name=Sound
Exec=python3 /usr/local/bin/menu.py
Icon=audio-headphones
NoDisplay=false
py script
/usr/local/bin/menu.py
#!/usr/bin/env python3
# Milos Pavlovic 2015 <mpsrbija@gmail.com>
#
#
# 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 Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
import os
import sys
import string
import subprocess
import shlex
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository.GdkPixbuf import Pixbuf
def execute(command):
"""function to exec switch"""
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return p.stdout
class Menu:
def destroy(self, widget, data=None):
Gtk.main_quit()
def action(self, widget, event, execc, data=None):
command = execc
subprocess.Popen(shlex.split(command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
Gtk.main_quit()
def __init__(self):
self.menu = Gtk.Menu()
""" This is how we close the script
connecting to the hide signal
"""
self.menu.connect("hide", self.destroy)
it=Gtk.IconTheme.get_default()
height = 100 # Height workaround
button= Gtk.Switch()
button.connect("notify::active", self.on_switch_activated)
cmd = 'amixer get Master | grep -q off ; echo $?'
result1 = os.popen(cmd)
result1 = result1.readline()
if int(result1) == 0:
button.set_active(False)
else:
button.set_active(True)
box = Gtk.Box(homogeneous =True)
img = Gtk.Image(xalign=0)
pixbuf=it.load_icon("emblem-sound",24,0)
img.set_from_pixbuf(pixbuf)
box.add(img)
box.add(button)
menuitem = Gtk.MenuItem()
menuitem.add(box)
menuitem.connect("button-press-event", self.mute_clicked)
menuitem.show()
self.menu.append(menuitem)
# adjustment (initial value, min value, max value,
# step increment - press cursor keys to see!,
# page increment - click around the handle to see!,
# page size - not used here)
ad1 = Gtk.Adjustment(0, 0, 100, 5, 10, 0)
cmd = "/usr/bin/amixer sget Master | grep '\[' "
result = os.popen(cmd)
result = result.readline()
find_start = result.find('[') + 1
find_end = result.find('%]', find_start)
audio_level = int(result[find_start:find_end])
ad1. set_value(audio_level)
# an horizontal scale
self.h_scale = Gtk.Scale(
orientation=Gtk.Orientation.HORIZONTAL, adjustment=ad1)
# of integers (no digits)
self.h_scale.set_digits(0)
# that can expand horizontally if there is space in the grid
self.h_scale.set_hexpand(True)
# that is aligned at the top of the space allowed in the grid
self.h_scale.set_valign(Gtk.Align.START)
# we connect the signal "value-changed" emitted by the scale with the callback
# function scale_moved
self.h_scale.connect("value-changed", self.scale_moved)
box = Gtk.Box()
box.add(self.h_scale)
menuitem = Gtk.MenuItem()
menuitem.add(box)
menuitem.show()
self.menu.append(menuitem)
self.menu.set_size_request(180, height) # Workaround for height
self.menu.popup(None, None, None, None, 0, Gtk.get_current_event_time())
self.menu.show_all()
# Let's set the voulume here
def scale_moved(self, event):
val = "{0}".format(int(self.h_scale.get_value()))
proc = subprocess.Popen('/usr/bin/amixer sset Master ' + str(val) + '% 2>&1 >/dev/null', shell=True, stdout=subprocess.PIPE)
proc.wait()
def on_switch_activated(self, switch, gparam):
if switch.get_active():
execute('amixer set Master unmute 2>&1 >/dev/null')
state = "on"
else:
execute('amixer set Master mute 2>&1 >/dev/null')
state = "off"
# Here we toogle the mute on and off when menuitem is clicked
def mute_clicked(self, event, data=None):
val = "toggle"
proc = subprocess.Popen('/usr/bin/amixer set Master ' + str(val) + ' 2>&1 >/dev/null', shell=True, stdout=subprocess.PIPE)
proc.wait()
def main(self):
Gtk.main()
if __name__ == "__main__":
app = Menu()
app.main()
The script closes when clicked outside of the menu.
There is a mute switch but will work as a button also.
reference from here >>> http://vsido.org/index.php?topic=1089.msg12301#msg12301 (http://vsido.org/index.php?topic=1089.msg12301#msg12301)
This is really cool misko_2083
I am going to play with this and get it to work on the top right tint2 and make it a default in VSIDO if that is all right with you
Well done mate, good solid work!
@misko_2083: Dang, works very well and easy to configure. Great addition, thanks - z
@ misko_2083
You should let everyone know in your first post that the menu.py file must be made executable...
Most know this or would figure it out, but having it there will save a step for you and for them... :D
Thanks
Here it is in the Panel Tint2 on VSIDO
(http://www.zimagez.com/miniature/screenshot2015-12-1622-53-08.php)
(http://www.zimagez.com/zimage/screenshot2015-12-1622-53-08.php)
Cool. Thank you. I'm not using tint2 currently, but this one goes directly to the goodies stash.
Quote from: VastOne on December 17, 2015, 02:11:38 AM
This is really cool misko_2083
I am going to play with this and get it to work on the top right tint2 and make it a default in VSIDO if that is all right with you
Well done mate, good solid work!
Thank you.
Sure, I'm Ok with that VSIDO get's a Volume control.
Quote from: zephyr on December 17, 2015, 04:14:50 AM
@misko_2083: Dang, works very well and easy to configure. Great addition, thanks - z
Thanks Z
Quote from: VastOne on December 17, 2015, 04:50:28 AM
@ misko_2083
You should let everyone know in your first post that the menu.py file must be made executable...
Most know this or would figure it out, but having it there will save a step for you and for them... :D
Thanks
If they are launching with python3 there is no need. ;)
Quote from: VastOne on December 17, 2015, 04:53:53 AM
Here it is in the Panel Tint2 on VSIDO
(http://www.zimagez.com/miniature/screenshot2015-12-1622-53-08.php)
(http://www.zimagez.com/zimage/screenshot2015-12-1622-53-08.php)
That's cool. Minimalistic approach but sometimes it takes some time to launch. It's faster if it stays in the memory. I'll post in the next comment, it probably won't fit in this one. :D
Quote from: Snap on December 17, 2015, 06:50:05 AM
Cool. Thank you. I'm not using tint2 currently, but this one goes directly to the goodies stash.
Welcome.
@VastOne I've been playing with this and made a lot of changes. Script makes an icon in the tray area, while it's in the memory the icon will be there.
Launches faster.
There is a right and left click menu now. Left click menu is the same except the label is added.
Right-click menu can launch PulseAudio Volume Control, Alsamixer and About dialog. Also there is a Quit option.
#!/usr/bin/env python3
# Milos Pavlovic 2015 <mpsrbija@gmail.com>
#
#
# 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 Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
import os
import sys
import string
import subprocess
import shlex
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository.GdkPixbuf import Pixbuf
# Here we define some icons
vsido_icon = "//usr/local/bin/images/vsidoorb_blk_sm_140.png"
notification_icon="/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
def execute(command):
"""function to exec switch"""
p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return p.stdout
class Menu:
def destroy(self, widget, data=None):
Gtk.main_quit()
def action(self, widget, event, execc, data=None):
command = execc
subprocess.Popen(shlex.split(command), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def __init__(self):
self.tray = Gtk.StatusIcon()
self.tray.set_from_file(notification_icon)
self.tray.connect('popup-menu', self.on_right_click)
self.tray.connect('activate', self.on_left_click)
self.tray.set_tooltip_text('VSIDO Volume Control')
def on_left_click(self, widget):
self.menu = Gtk.Menu()
self.menu.set_border_width(6)
height=115
it=Gtk.IconTheme.get_default()
button= Gtk.Switch()
button.connect("notify::active", self.on_switch_activated)
cmd = 'amixer get Master | grep -q off ; echo $?'
result1 = os.popen(cmd)
result1 = result1.readline()
if int(result1) == 0:
button.set_active(False)
else:
button.set_active(True)
box1 = Gtk.Grid(column_homogeneous=True,row_spacing=10)
img = Gtk.Image(xalign=0)
pixbuf=it.load_icon("emblem-sound",24,0)
img.set_from_pixbuf(pixbuf)
box1.add(img)
box1.attach_next_to(button, img, Gtk.PositionType.RIGHT, 1, 1)
menuitem_switch = Gtk.MenuItem()
menuitem_switch.add(box1)
menuitem_switch.connect("button-press-event", self.mute_clicked)
menuitem_switch.show()
self.menu.append(menuitem_switch)
vsido=Gtk.Label("VSIDO Volume Control")
menuitem_vsido = Gtk.MenuItem()
menuitem_vsido.add(vsido)
menuitem_vsido.set_sensitive(False)
self.menu.append(menuitem_vsido)
# adjustment (initial value, min value, max value,
# step increment - press cursor keys to see!,
# page increment - click around the handle to see!,
# page size - not used here)
ad1 = Gtk.Adjustment(0, 0, 100, 5, 10, 0)
cmd = "/usr/bin/amixer sget Master | grep '\[' "
result = os.popen(cmd)
result = result.readline()
find_start = result.find('[') + 1
find_end = result.find('%]', find_start)
audio_level = int(result[find_start:find_end])
ad1. set_value(audio_level)
# an horizontal scale
self.h_scale = Gtk.Scale(
orientation=Gtk.Orientation.HORIZONTAL, adjustment=ad1)
# of integers (no digits)
self.h_scale.set_digits(0)
# that can expand horizontally if there is space in the grid
self.h_scale.set_hexpand(True)
# that is aligned at the top of the space allowed in the grid
self.h_scale.set_valign(Gtk.Align.START)
# we connect the signal "value-changed" emitted by the scale with the callback
# function scale_moved
self.h_scale.connect("value-changed", self.scale_moved)
box2 = Gtk.Grid()
box2.add(self.h_scale)
menuitem_scale = Gtk.MenuItem()
menuitem_scale.add(box2)
menuitem_scale.show()
self.menu.append(menuitem_scale)
self.menu.set_size_request(180, height) # Workaround for height
self.menu.popup(None, None, None, self.tray, 1, Gtk.get_current_event_time())
self.menu.show_all()
def on_right_click(self, icon, button, time):
it=Gtk.IconTheme.get_default()
# Pulse audio-menu item
box = Gtk.Box()
box.set_spacing(10)
self.menu = Gtk.Menu()
img = Gtk.Image()
try:
pixbuf=it.load_icon('arts', 16,0)
except:
pixbuf=it.load_icon('gtk-stop', 16,0)
img.set_from_pixbuf(pixbuf)
label = Gtk.Label("PulseAudio Volume")
box.add(img)
box.add(label)
box.show()
pulse = Gtk.MenuItem()
pulse.add(box)
execc="launch pavucontrol"
pulse.connect("button-press-event", self.action, execc)
# Alsa mixer-menu item
box = Gtk.Box()
box.set_spacing(10)
self.menu = Gtk.Menu()
img = Gtk.Image()
try:
pixbuf=it.load_icon('audio-volume-high', 16,0)
except:
pixbuf=it.load_icon('gtk-stop', 16,0)
img.set_from_pixbuf(pixbuf)
label = Gtk.Label("AlsaMixer")
box.add(img)
box.add(label)
box.show()
alsa = Gtk.MenuItem()
alsa.add(box)
execc="xfce4-terminal -e alsamixer --title=AlsaMixer"
alsa.connect("button-press-event", self.action, execc)
# About-menu item
box = Gtk.Box()
box.set_spacing(10)
self.menu = Gtk.Menu()
img = Gtk.Image()
try:
pixbuf=it.load_icon('gtk-info', 16,0)
except:
pixbuf=it.load_icon('gtk-stop', 16,0)
img.set_from_pixbuf(pixbuf)
label = Gtk.Label("About")
box.add(img)
box.add(label)
about = Gtk.MenuItem()
about.add(box)
# Quit-menu item
box = Gtk.Box()
box.set_spacing(10)
self.menu = Gtk.Menu()
img = Gtk.Image()
try:
pixbuf=it.load_icon('gtk-stock-cancel', 16,0)
except:
pixbuf=it.load_icon('gtk-stop', 16,0)
label = Gtk.Label("About")
label.set_label("Quit")
box.add(img)
box.add(label)
quit = Gtk.MenuItem()
quit.add(box)
about.connect("button-press-event", self.show_about_dialog)
quit.connect("activate", Gtk.main_quit)
self.menu.append(pulse)
self.menu.append(alsa)
separator = Gtk.SeparatorMenuItem()
self.menu.append(separator)
self.menu.append(about)
separator = Gtk.SeparatorMenuItem()
self.menu.append(separator)
self.menu.append(quit)
self.menu.show_all()
self.menu.popup(None, None, None, self.tray, button, time)
def show_about_dialog(self, widget, time):
about_dialog = Gtk.AboutDialog()
about_dialog.set_destroy_with_parent (True)
about_dialog.set_program_name("VSIDO Volume Control")
about_dialog.set_website('http://vsido.org')
about_dialog.set_website_label('vsido.org')
about_dialog.set_icon(Pixbuf.new_from_file(vsido_icon))
about_dialog.set_logo(Pixbuf.new_from_file(vsido_icon))
about_dialog.set_copyright('Copyright 2015')
about_dialog.set_comments((u'A tool for setting Volume'))
about_dialog.set_license('''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 Street, Fifth Floor, Boston,
MA 02110-1301, USA. ''')
about_dialog.set_authors([u'Milos Pavlovic <mpsrbija@gmail.com>'])
about_dialog.run()
about_dialog.destroy()
# Let's set the voulume here
def scale_moved(self, event, data=None):
val = "{0}".format(int(self.h_scale.get_value()))
proc = subprocess.Popen('/usr/bin/amixer sset Master ' + str(val) + '% 2>&1 >/dev/null', shell=True, stdout=subprocess.PIPE)
proc.wait()
def on_switch_activated(self, switch, gparam):
if switch.get_active():
execute('amixer set Master unmute 2>&1 >/dev/null')
state = "on"
else:
execute('amixer set Master mute 2>&1 >/dev/null')
state = "off"
# Here we toogle the mute on and off when menuitem is clicked
def mute_clicked(self, event, data=None):
val = "toggle"
proc = subprocess.Popen('/usr/bin/amixer set Master ' + str(val) + ' 2>&1 >/dev/null', shell=True, stdout=subprocess.PIPE)
proc.wait()
def main(self):
Gtk.main()
if __name__ == "__main__":
app = Menu()
app.main()
Incredible... These are the exact enhancements I was going to recommend to you
That is fantastic, thank you for this. This will be a standard on the next ISO's I build
Great work!
about this line:
vsido_icon = "//usr/local/bin/images/vsidoorb_blk_sm_140.png"
is "//usr" a typo, or does the "//" mean something in python3?
Good catch PackRat... the line just below that also has one where:
notification_icon="/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
has no spacing after the icon= area here
notification_icon="/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
should be
notification_icon = "/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
... but I ran the code with the errors and without and the code works just the same
Quote from: PackRat on December 18, 2015, 02:10:04 AM
about this line:
vsido_icon = "//usr/local/bin/images/vsidoorb_blk_sm_140.png"
is "//usr" a typo, or does the "//" mean something in python3?
Ah, it was a typo. :D
Nice catch @PackRat
It's working because UNC paths can be used. Those are used when accessing directories and files on a network, but works on local files too.
It should be like this:
vsido_icon = "/usr/local/bin/images/vsidoorb_blk_sm_140.png"
Quote from: VastOne on December 18, 2015, 03:24:00 AM
Good catch PackRat... the line just below that also has one where:
notification_icon="/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
has no spacing after the icon= area here
notification_icon="/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
should be
notification_icon = "/usr/share/icons/LinuxLex-8/status/24/notification-audio-volume-high.png"
... but I ran the code with the errors and without and the code works just the same
@VastOne Python doesn't care about spaces just the indent. Though it looks better if it's properly formated. :)
Ran into an issue with this on the new ISO builds
vastone@vsido:~$ python3 /usr/local/bin/menu.py
Traceback (most recent call last):
File "/usr/local/bin/menu.py", line 27, in <module>
require_version("Gtk", "3.0")
File "/usr/lib/python3/dist-packages/gi/__init__.py", line 102, in require_version
raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gtk not available
Python3 is installed
libgtk-3-0 is installed
Not sure what the cause is, I searched for a solution but came up empty ... It works flawlessly on this machine which leads me to believe there is a dependency this machine has met but the build environment does not have
I have a new install of this ready for any testing ... Let me know what is needed
Quote from: VastOne on December 19, 2015, 05:21:32 AM
Ran into an issue with this on the new ISO builds
vastone@vsido:~$ python3 /usr/local/bin/menu.py
Traceback (most recent call last):
File "/usr/local/bin/menu.py", line 27, in <module>
require_version("Gtk", "3.0")
File "/usr/lib/python3/dist-packages/gi/__init__.py", line 102, in require_version
raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gtk not available
Python3 is installed
libgtk-3-0 is installed
Not sure what the cause is, I searched for a solution but came up empty ... It works flawlessly on this machine which leads me to believe there is a dependency this machine has met but the build environment does not have
I have a new install of this ready for any testing ... Let me know what is needed
This one :)
apt-cache policy gir1.2-gtk-3.0
gir1.2-gtk-3.0:
Installed: 3.18.5-1
Candidate: 3.18.6-1
Version table:
3.18.6-1 500
500 http://ftp.us.debian.org/debian sid/main i386 Packages
*** 3.18.5-1 100
100 /var/lib/dpkg/status
That was it... that brought in several more dependencies but we can live with their size
Thanks misko... 8)