From 57443672e78c330244cbbe70166c42c050f9037b Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Mon, 29 Jul 2024 15:59:16 +0500 Subject: [PATCH 1/8] Update version in about dialog --- volwheel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volwheel b/volwheel index 6db957b..6021c8c 100755 --- a/volwheel +++ b/volwheel @@ -28,7 +28,7 @@ use ConfDialog; use MiniMixer; use constant APPNAME => "VolWheel"; -use constant VERSION => "0.2.8"; # 2010-08-01 +use constant VERSION => "0.2.9"; # 2018-10-05 use constant INTRVAL => 0.18; # Lower this variable if you find volwheel # not reactive enough. This will consume -- 2.44.2 From fe0977ddc15a8a330449443328faedffd4882594 Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Wed, 17 Jul 2024 20:33:13 +0500 Subject: [PATCH 2/8] Optimized alsa backend (requires amixer fix) Amixer supports two modes for efficient alsa operation: - sevents command to monitor outside changes. - stdin mode to both set volume and get current volume (with noop sset request like "sset Master 0+"). However amixer must be fixed to not ignore outside alsa events (from other alsa volume control applications). --- ChangeLog | 3 + TODO | 1 - lib/Alsa.pm | 203 ++++++++++++++++++++++++++++++++++++++------------- lib/OSS.pm | 2 + lib/Scale.pm | 4 +- volwheel | 38 +++++----- 6 files changed, 182 insertions(+), 69 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3fb0de3..1cad3b6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ + 2024-07-17 - master + * Optimized Alsa backend + 2018-10-05 - v0.2.9 * Back from the grave, import to github * Updated README diff --git a/TODO b/TODO index 3c3b759..fd68669 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,2 @@ + Add configurable mouse bindings -+ Optimize Alsa backend + use drop-down menus instead of input-text diff --git a/lib/Alsa.pm b/lib/Alsa.pm index e20d46d..2bcce3e 100644 --- a/lib/Alsa.pm +++ b/lib/Alsa.pm @@ -17,72 +17,176 @@ package Alsa; # along with this program. If not, see . use strict; +use IPC::Open2; + +our $intrval = 0; + +my ($i_dirty, $i_pswitch, $i_vol, $i_muted) = (0,1,2,3); +my (%channels, $channel); +my ($info_pid, $info_fh, $ctl_fh); +my ($ev_pid, $ev_fh, $ev_cb, $ev_skip); + +sub init { + ($ev_cb, $channel) = @_; + + $ev_pid = open $ev_fh, '-|', 'stdbuf', '-oL', 'amixer', 'sevents'; + $info_pid = open2 $info_fh, $ctl_fh, 'stdbuf', '-oL', 'amixer', '--stdin'; + $info_fh->blocking(0); + $ctl_fh->autoflush(1); + $ev_fh->blocking(0); + + return $ev_fh; +} + +sub deinit { + kill 'TERM', $ev_pid, $info_pid; + close $ctl_fh; + close $ev_fh; + close $info_fh +} + +sub on_event { + my $flag = 0; + + while (defined sysread $ev_fh, my $buf, 2048) { + my $accum .= $buf; + while ($accum =~ s/(.*)\n//o) + { + $_ = $1; + last if not defined; + if (/^event value: '([^']*)',(\d+)/) { + if ($ev_skip) { + $ev_skip--; + next; + } + $flag = 1 if $1 eq $channel; + $channels{ $2 ? "$1,$2" : $1 }->[ $i_dirty ] = 1; + } + elsif (/^event add: '([^']*)',(\d+)/) { + $channels{ $2 ? "$1,$2" : $1 } = [ 1, 0, 0, 0 ]; + } + } + } + if ($flag) { + print $ctl_fh "sset $channel 0+\n"; + on_event_info(); + ${ev_cb}->(); + } + + return 1; +} + +sub on_event_info { + my ($stage, $chref); + + (my $rfd_ref) = (my $rfd) = ''; + vec( $rfd_ref, fileno $info_fh, 1) = 1; + 0 until (select( $rfd = $rfd_ref, undef, undef, undef) != -1); + + while (defined sysread $info_fh, my $buf, 2048) { + my $accum .= $buf; + while ($accum =~ s/^(.*\n)//o) + { + $_ = $1; + if ($stage == 0) { + next if !/^Simple mixer control '([^']*)',(\d+)/o; + $chref = $channels{ $2 ? "$1,$2" : $1 }; + $chref->[$i_dirty] = 0; + $stage++; + next; + } + if ($stage == 1) { + next if !/^\s*Capabilities:/gco; + $chref->[$i_pswitch] = 1 if /\bpswitch\b/; + $stage++; + next; + } + if ($stage == 2) { + next if !/%/; + if ( /\[(\d+)%\].*\[(o(?:n|ff))\]/go ) { + $chref->[$i_vol] = $1; + $chref->[$i_muted] + = $2 eq 'off' ? 1 # real mute + : $1 == 0 ? 2 # fake mute + : 0; # normal + } + $stage = 0; + next; + } + } + } +} sub volume { - my $channel = shift; + my $chref = $channels{ $channel }; - my $volume = `amixer get "$channel" | grep -m1 %`; - $volume =~ /^.*\[([0-9]*)%.*$/; - return $1; - + if ($chref->[$i_dirty]) { + print $ctl_fh "sset $channel 0+\n"; + on_event_info(); + } + return $chref->[$i_vol]; } sub is_muted { - my $channel = shift; + my $chref = $channels{ $channel }; - if (`amixer get "$channel" | grep off` ne "") { - return (1); # real mute + if ($chref->[$i_dirty]) { + print $ctl_fh "sset $channel 0+\n"; + on_event_info(); } - elsif ( volume($channel) == 0 ) { - return (2); # fake mute - } - else { - return (0); # normal - } - + return ($chref->[$i_muted]); } # Returns both the volume and if muted sub status { - my $channel = shift; + my $chref = $channels{ $channel }; - my $volume = volume($channel); - - if (`amixer get "$channel" | grep off` ne "") { - return (1, $volume); # real mute + if ($chref->[$i_dirty]) { + print $ctl_fh "sset $channel 0+\n"; + on_event_info(); } - elsif ( $volume == 0 ) { - return (1, $volume); # fake mute - } - else { - return (0, $volume); # normal - } - + my ($volume, $muted) = ( + $chref->[$i_vol], + $chref->[$i_muted], + ); + return ($muted || $volume == 0, $volume); } sub mute { my $channel = shift; + my $chref = $channels{ $channel }; - my ($muted, $volume) = status($channel); + if ($chref->[$i_dirty]) { + print $ctl_fh "sset $channel 0+\n"; + on_event_info(); + } + my ($muted, $volume, $pswitch) = ( + $chref->[$i_muted], + $chref->[$i_vol], + $chref->[$i_pswitch], + ); # We can't mute if it's already muted ! if ($muted) { return -1 } - if (`amixer get "$channel" | grep pswitch` ne "") { + $ev_skip++; + if ($pswitch) { # The channel pswitch and thus we can do a real mute - system "amixer set \"$channel\" toggle > /dev/null"; + print $ctl_fh "set $channel toggle\n"; } else { # Fake mute - system "amixer set \"$channel\" 100%- > /dev/null"; + print $ctl_fh "set $channel 0%\n"; } + on_event_info(); + ${ev_cb}->(); return $volume; @@ -96,14 +200,18 @@ sub unmute { my $muted = is_muted($channel); if ($muted == 1) { - system "amixer set \"$channel\" toggle > /dev/null"; + $ev_skip++; + print $ctl_fh "set $channel toggle\n"; } elsif ($muted == 2) { - system "amixer set \"$channel\" $old_volume%+ > /dev/null"; + $ev_skip++; + print $ctl_fh "set $channel $old_volume%\n"; } else { return -1; # The channel is not muted ! } + on_event_info(); + ${ev_cb}->(); } @@ -111,7 +219,10 @@ sub unmute { sub volume_up { my ($channel, $increment) = @_; - system "amixer set \"$channel\" $increment%+ > /dev/null"; + $ev_skip++; + print $ctl_fh "set $channel $increment%+\n"; + on_event_info(); + ${ev_cb}->(); } @@ -119,7 +230,10 @@ sub volume_up { sub volume_down { my ($channel, $increment) = @_; - system "amixer set \"$channel\" $increment%- > /dev/null"; + $ev_skip++; + print $ctl_fh "set $channel $increment%-\n"; + on_event_info(); + ${ev_cb}->(); } @@ -128,25 +242,16 @@ sub set_volume { my ($channel, $value) = @_; unmute($channel, 0); - system("amixer set \"$channel\" $value% > /dev/null"); + $ev_skip++; + print $ctl_fh "set $channel $value%\n"; + on_event_info(); + ${ev_cb}->(); } sub get_channels { - - my @text = `amixer scontrols`; - my @channels; - - foreach my $line (@text) { - $line =~ /^.*'(.*)',(\d)$/; - my $chan = $1; - if ($2 != 0) { $chan .= ",$2"; } - push (@channels,$chan); - } - - return @channels; - + return sort keys %channels; } 1; diff --git a/lib/OSS.pm b/lib/OSS.pm index 8f662a7..e135691 100644 --- a/lib/OSS.pm +++ b/lib/OSS.pm @@ -20,6 +20,8 @@ package OSS; use strict; +our $intrval = 0.15; + sub volume { my $channel = shift; diff --git a/lib/Scale.pm b/lib/Scale.pm index 0522f6c..e7fee93 100644 --- a/lib/Scale.pm +++ b/lib/Scale.pm @@ -36,12 +36,12 @@ sub new { $self->{_scale}->set_value(main::get_volume($self->{_channel})); $self->{_scale}->signal_connect('value-changed' => sub { #print("interval: " . tv_interval($self->{_tv}) . "\n"); - if (tv_interval($self->{_tv}) < $::intrval) { return; } + if ($::intrval && tv_interval($self->{_tv}) < $::intrval) { return; } main::set_volume( $self->{_scale}->get_value, $self->{_channel} ); if ($self->{_channel} eq $::opt->channel) { main::update_icon(); } - $self->{_tv} = [ gettimeofday ]; + $self->{_tv} = [ gettimeofday ] if $::intrval; }); $self->add($self->{_scale}); diff --git a/volwheel b/volwheel index 6021c8c..9093443 100755 --- a/volwheel +++ b/volwheel @@ -30,17 +30,18 @@ use MiniMixer; use constant APPNAME => "VolWheel"; use constant VERSION => "0.2.9"; # 2018-10-05 -use constant INTRVAL => 0.18; # Lower this variable if you find volwheel - # not reactive enough. This will consume - # more CPU though. - # M A I N # our $prefix = "/usr/local"; -our $intrval = INTRVAL; +our $intrval; our $opt = Conf->new; $opt->read_conf; +{ + no strict 'refs'; + my $driver = $opt->driver; + $intrval = ${"${driver}::intrval"}; +} my $multiplicator = 1; @@ -62,15 +63,21 @@ my $icon = Gtk2::StatusIcon->new; $icon->signal_connect('button_release_event', \&click_handler); $icon->signal_connect('popup-menu', \&popup); $icon->signal_connect('scroll_event', \&scroll_handler); -update_icon(); - -# the refresh loop -my $loop = Glib::Timeout->add_seconds($opt->loop_time, \&update_icon); my $tv = [ gettimeofday ]; +# the refresh loop +if ($opt->driver eq "Alsa") { + my $efh = Alsa::init(\&update_icon, $opt->channel); + my $loop = Glib::IO->add_watch(fileno $efh, 'in', \&Alsa::on_event); +} elsif ($opt->driver eq "OSS") { + my $loop = Glib::Timeout->add_seconds($opt->loop_time, \&update_icon); +} + +update_icon(); Gtk2->main; +if ($opt->driver eq "Alsa") { Alsa::deinit() } # S U B S # @@ -82,7 +89,7 @@ sub volup { } } elsif ($opt->driver eq "OSS") { - OSS::volume_up( $opt->channel, $opt->increment ); + OSS::volume_up( $opt->channel, $opt->increment * $multiplicator ); } } @@ -93,7 +100,7 @@ sub voldown { } } elsif ($opt->driver eq "OSS") { - OSS::volume_down( $opt->channel, $opt->increment ); + OSS::volume_down( $opt->channel, $opt->increment * $multiplicator ); } } @@ -122,8 +129,7 @@ sub get_volume { if ($opt->driver eq "Alsa") { my ($muted, $volume) = Alsa::status($channel); - if ($muted > 0) { return 0 } - else { return $volume } + return $muted ? 0 :$volume; } elsif ($opt->driver eq "OSS") { return ( OSS::volume($channel) ); @@ -168,18 +174,16 @@ sub click_handler { # Middle click elsif ($event->button eq 2) { toggle(); - update_icon(); } } sub scroll_handler { my ($check, $event) = @_; #print("interval: " . tv_interval($tv) . "\n"); - if (tv_interval($tv) < INTRVAL) { $multiplicator++; return; } + if ($intrval && tv_interval($tv) < $intrval) { $multiplicator++; return; } if ("up" eq $event->direction) { volup(); } else { voldown(); } - update_icon(); - $tv = [ gettimeofday ]; + $tv = [ gettimeofday ] if $intrval; $multiplicator = 1; } -- 2.44.2 From 4a9e85b61437e601d9be9b31f38a8374b8172b03 Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Wed, 17 Jul 2024 22:37:45 +0500 Subject: [PATCH 3/8] Minimize icon updates (only when changed) --- lib/ConfDialog.pm | 20 ++++++-------------- volwheel | 11 +++++++++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/ConfDialog.pm b/lib/ConfDialog.pm index 6b19b1b..d92e527 100644 --- a/lib/ConfDialog.pm +++ b/lib/ConfDialog.pm @@ -96,25 +96,17 @@ sub show { my $lbl_icomod = Gtk2::Label->new("Icon Mode"); my $radio_stc = Gtk2::RadioButton->new_with_label(undef, "Static"); $radio_stc->signal_connect('toggled' => sub { - $filechooser->set_sensitive(1); - $combo_theme->set_sensitive(0); - $::opt->icon_static(1); + my $active = $radio_stc->get('active'); + $filechooser->set_sensitive($active); + $combo_theme->set_sensitive(! $active); + $::opt->icon_static($active); main::update_icon(); }); my $radio_dyn = Gtk2::RadioButton->new_with_label($radio_stc, "Dynamic"); - $radio_dyn->signal_connect('toggled' => sub { - $filechooser->set_sensitive(0); - $combo_theme->set_sensitive(1); - $::opt->icon_static(0); - main::update_icon(); - }); - if ($::opt->icon_static == 1) { + if ($::opt->icon_static) { $radio_stc->set_active(1); - $radio_dyn->set_active(0); - } - else { + } else { $radio_dyn->set_active(1); - $radio_stc->set_active(0); } $hbox4->pack_start($lbl_icomod, 0, 0, 5); $hbox4->pack_end($radio_stc, 0, 0, 5); diff --git a/volwheel b/volwheel index 9093443..2185dcc 100755 --- a/volwheel +++ b/volwheel @@ -210,14 +210,21 @@ sub popup { } sub update_icon { + CORE::state $icon_number = -1; + CORE::state $old_icon_num = -1; my $volume = get_volume(); if ($opt->icon_static) { + $icon_number = $old_icon_num = -1; $icon->set_from_file($opt->icon_path); } else { - my $icon_number = get_icon_number($volume); - $icon->set_from_file("$prefix/share/volwheel/icons/".$opt->icon_theme."/$icon_number.png"); + $icon_number = get_icon_number($volume); + if ($old_icon_num ne $icon_number) + { + $icon->set_from_file("$prefix/share/volwheel/icons/".$opt->icon_theme."/$icon_number.png"); + $old_icon_num = $icon_number; + } } $icon->set_tooltip_text($opt->channel." : $volume%"); -- 2.44.2 From 7fc48afe718f7cc614d8a284a7c7c68fdcc8302a Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Sat, 20 Jul 2024 00:55:42 +0500 Subject: [PATCH 4/8] Select channels from list instead of manual type --- ChangeLog | 1 + TODO | 1 - lib/Alsa.pm | 9 ++++- lib/Conf.pm | 36 +++++++++++++++---- lib/ConfDialog.pm | 91 +++++++++++++++++++++++++---------------------- 5 files changed, 87 insertions(+), 51 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1cad3b6..3520d2a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,6 @@ 2024-07-17 - master * Optimized Alsa backend + * use drop-down menu for icon channel and checkboxes list for minimixer instead of text entries 2018-10-05 - v0.2.9 * Back from the grave, import to github diff --git a/TODO b/TODO index fd68669..f3575d7 100644 --- a/TODO +++ b/TODO @@ -1,2 +1 @@ + Add configurable mouse bindings -+ use drop-down menus instead of input-text diff --git a/lib/Alsa.pm b/lib/Alsa.pm index 2bcce3e..daaaf78 100644 --- a/lib/Alsa.pm +++ b/lib/Alsa.pm @@ -251,7 +251,14 @@ sub set_volume { sub get_channels { - return sort keys %channels; + my @array; + open my $fd, '-|', 'amixer', 'scontrols'; + while (<$fd>) { + /'([^']*)'/; + push @array, $1; + } + return @array; + #return sort keys %channels; } 1; diff --git a/lib/Conf.pm b/lib/Conf.pm index 58c8790..6de4ebb 100644 --- a/lib/Conf.pm +++ b/lib/Conf.pm @@ -18,6 +18,9 @@ package Conf; use strict; +use Alsa; +use OSS; + sub new { my $class = shift; my $self = {}; @@ -51,6 +54,17 @@ sub get_conf_path { } +my @pathv = split( ':', $ENV{PATH}); + +sub alt_exe { + for (@_) { + for my $dir (@pathv) { + my $exe = "$dir/$_"; + return $_ if -x $exe; + } + } +} + sub read_conf { my $self = shift; @@ -59,7 +73,8 @@ sub read_conf { if (-r "$path/volwheel") { open (CONFIG, "$path/volwheel"); - my @config = ; + chomp( my @config = ); + close CONFIG; if ($config[0] && $config[0] ne "") { $self->{_channel} = $config[0]; } if ($config[1] && $config[1] ne "") { $self->{_mixer} = $config[1]; } if ($config[2] && $config[2] =~ /^\d+$/) { $self->{_increment} = $config[2]; } @@ -67,17 +82,26 @@ sub read_conf { if ($config[4] && $config[4] ne "") { $self->{_icon_path} = $config[4]; } if ($config[5] && $config[5] =~ /^(0|1)$/) { $self->{_icon_static} = $config[5]; } if ($config[6] && $config[6] ne "") { $self->{_driver} = $config[6]; } - if ($config[7] && $config[7] =~ /:/) { $line7 = $config[7]; } - close CONFIG; - chomp %{$self}; - chomp $line7; - $self->channel_list( split(":", $line7) ); + if ($config[7] && $config[7] =~ /:/) { + @{ $self->{_channel_list} } = split(':', $config[7]); + } } else { + $self->{_driver} = (-f '/dev/mixer') ? 'OSS' : 'Alsa'; + my $drv = $self->{_driver}; + # autodetect the mixer if (-x "/usr/bin/gnome-alsamixer") { $self->{_mixer} = "gnome-alsamixer"; } if (-x "/usr/bin/xfce4-mixer") { $self->{_mixer} = "xfce4-mixer"; } if (-x "/usr/bin/ossxmix") { $self->{_mixer} = "ossxmix"; } + + # autodetect channels + my @channel_list; + foreach ($drv == "Alsa" ? Alsa::get_channels : OSS::get_channels) { + push @channel_list, $_; + } + $self->{_channel_list} = \@channel_list; + $self->{_channel } = $channel_list[0]; } } diff --git a/lib/ConfDialog.pm b/lib/ConfDialog.pm index d92e527..ff11a19 100644 --- a/lib/ConfDialog.pm +++ b/lib/ConfDialog.pm @@ -18,6 +18,8 @@ package ConfDialog; use strict; use Glib; +use Alsa; +use OSS; sub show { @@ -63,10 +65,23 @@ sub show { my $hbox1 = Gtk2::HBox->new(0, 2); my $lbl_channel = Gtk2::Label->new("Default channel"); - my $entry_channel = Gtk2::Entry->new; - $entry_channel->set_text($::opt->channel); + my $combo_channel = Gtk2::ComboBox->new_text; + $combo_channel->set_size_request(160, 30); + my $drv = $combo_driver->get_active_text; + foreach ($drv == "Alsa" ? Alsa::get_channels : OSS::get_channels) { + if ($_ eq $::opt->channel) { + $combo_channel->prepend_text($_); + } else { + $combo_channel->append_text($_); + } + } + $combo_channel->set_active(0); + $combo_channel->signal_connect('changed' => sub { + $::opt->channel($combo_channel->get_active_text); + main::update_icon(); + }); $hbox1->pack_start($lbl_channel, 0, 1, 5); - $hbox1->pack_end($entry_channel, 0, 1, 5); + $hbox1->pack_end($combo_channel, 0, 1, 5); $vbox1->pack_start($hbox1, 0, 1, 2); my $hbox2 = Gtk2::HBox->new(0, 2); @@ -159,53 +174,43 @@ sub show { ("Choose which channels to show in the MiniMixer"); $tab2_vbox->pack_start($tab2_label, 0, 0, 10); - my $list_hbox = Gtk2::HBox->new(0, 10); - - my $model = Gtk2::ListStore->new('Glib::String'); - foreach ($::opt->channel_list) { - $model->set($model->append, 0, $_); + my $model = Gtk2::ListStore->new('Glib::Boolean', 'Glib::String'); + foreach ($drv == "Alsa" ? Alsa::get_channels : OSS::get_channels) { + my $text = $_; + my $flag = 0; + foreach ($::opt->channel_list) { + if ($_ eq $text) { + $flag = 1; + last; + } + } + $model->set($model->append, 0, $flag, 1, $_); } my $view = Gtk2::TreeView->new($model); - my $cell = Gtk2::CellRendererText->new; - $cell->set_property('editable', 1); - $cell->signal_connect('edited' => sub { - my ($cell, $pathstring, $newtext) = @_; + + my $cell = Gtk2::CellRendererToggle->new; + $cell->signal_connect('toggled' => sub { + my ($cell, $pathstring) = @_; my $path = Gtk2::TreePath->new_from_string($pathstring); my $iter = $model->get_iter($path); - $model->set ($iter, 0, $newtext); + $model->set ($iter, 0, ! $cell->get_active); }); + my $column = Gtk2::TreeViewColumn->new_with_attributes('', $cell, + active => 0,); + $view->append_column($column); + + my $cell = Gtk2::CellRendererText->new; my $column = Gtk2::TreeViewColumn->new_with_attributes('Channel Name', $cell, - text => 0,); + text => 1,); $view->append_column($column); + + $view->set_headers_visible(0); $view->set_reorderable(1); - $view->set_size_request(-1, 160); - $model->signal_connect('row-inserted', sub { - my ($model, $path, $iter) = @_; - $view->set_cursor_on_cell($path, $column, $cell, 1); - # FFFFFFFFFFFFFFFFFFFFFFFFFFFUUUUUUUUUUUUUUUUUUUUUUU- - }); - $list_hbox->pack_start($view, 1, 1, 10); + $view->set_size_request(-1, -1); - $tab2_vbox->pack_start($list_hbox, 0, 0, 0); + $tab2_vbox->pack_start($view, 1, 1, 0); - my $buttons_hbox = Gtk2::HBox->new(1, 10); - my $add_btn = Gtk2::Button->new_from_stock("gtk-add"); - $add_btn->signal_connect('clicked', sub { - $model->set($model->append, 0, "New Channel"); - }); - $buttons_hbox->pack_start($add_btn, 0, 0, 10); - my $del_btn = Gtk2::Button->new_from_stock("gtk-remove"); - $del_btn->signal_connect('clicked', sub { - my $selection = $view->get_selection; - my @iter = $selection->get_selected; - if ($iter[1] ne "") { - $model->remove($iter[1]); - } - }); - $buttons_hbox->pack_start($del_btn, 0, 0, 10); - - $tab2_vbox->pack_start($buttons_hbox, 0, 0, 0); # END OF SECOND TAB # START THIRD TAB @@ -219,7 +224,7 @@ sub show { $notebook->append_page($vbox, "General"); $notebook->append_page($tab2_vbox, "MiniMixer"); #$notebook->append_page($tab3_vbox, "Mouse Bindings"); - $main_vbox->pack_start($notebook, 1, 0, 5); + $main_vbox->pack_start($notebook, 1, 1, 5); ## # BOTTOM @@ -228,7 +233,7 @@ sub show { $btn_cancel->signal_connect('clicked' => sub { $winconf->destroy }); my $btn_save = Gtk2::Button->new_from_stock('gtk-save'); $btn_save->signal_connect('clicked' => sub { - my $temp = $entry_channel->get_text; + my $temp = $combo_channel->get_active_text; if ( $temp eq "" ) { return 666; } $temp =~ s/^"(.*)"$/$1/; # No quotes ! $::opt->driver($combo_driver->get_active_text); @@ -241,7 +246,7 @@ sub show { $model->foreach(sub { my ($model, $path, $iter) = @_; my @values = $model->get($iter); - push @array, $values[0] unless ($values[0] eq "New Channel"); + push (@array, $values[1]) if $values[0]; return 0; # required by foreach() }); $::opt->channel_list(@array); @@ -250,7 +255,7 @@ sub show { }); $hbox_bottom->add($btn_cancel); $hbox_bottom->add($btn_save); - $main_vbox->pack_start($hbox_bottom, 1, 0, 5); + $main_vbox->pack_start($hbox_bottom, 0, 0, 5); # END OF BOTTOM $winconf->add($main_vbox); -- 2.44.2 From e63029b11cc11e965e4743da4cfa6781658f20cb Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Mon, 29 Jul 2024 16:53:22 +0500 Subject: [PATCH 5/8] Enhance mixer auto-detection --- ChangeLog | 1 + lib/Conf.pm | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/ChangeLog b/ChangeLog index 3520d2a..fd73011 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 2024-07-17 - master * Optimized Alsa backend * use drop-down menu for icon channel and checkboxes list for minimixer instead of text entries + * Enhanced mixer auto-detection 2018-10-05 - v0.2.9 * Back from the grave, import to github diff --git a/lib/Conf.pm b/lib/Conf.pm index 6de4ebb..6ed95d4 100644 --- a/lib/Conf.pm +++ b/lib/Conf.pm @@ -95,6 +95,32 @@ sub read_conf { if (-x "/usr/bin/xfce4-mixer") { $self->{_mixer} = "xfce4-mixer"; } if (-x "/usr/bin/ossxmix") { $self->{_mixer} = "ossxmix"; } + # autodetect the mixer + my $sel = alt_exe ( + $drv eq 'Alsa' ? ( + 'gnome-alsamixer', 'xfce4-mixer', 'retrovol', 'qasmixer', 'alsamixergui', + 'kmix', 'pavucontrol', + ) : ( + 'aumix-gtk', 'ossxmix', + 'kmix', 'pavucontrol', + ) + ); + unless (defined $sel) + { + my $term = alt_exe ( + 'gnome-terminal', 'mate-terminal', 'xfce4-terminal', + 'lxterminal', 'roxterm', 'terminology', 'qterminal', + 'alacritty', 'kitty', 'wezterm', + 'mlterm', 'aterm', 'urxvt', 'xterm', 'st' + ); + my $cmix = alt_exe ( + $drv eq 'Alsa' ? ('alsamixer', 'pulsemixer', 'pamix') + : ('aumix', 'pulsemixer', 'pamix') + ); + $sel = "$term -e '$cmix'" if ($cmix && $term); + } + $self->{_mixer} = "$sel" if $sel; + # autodetect channels my @channel_list; foreach ($drv == "Alsa" ? Alsa::get_channels : OSS::get_channels) { -- 2.44.2 From 24146ae50344e3944fd81e6488a321030e85e6bd Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Mon, 29 Jul 2024 19:54:44 +0500 Subject: [PATCH 6/8] Enhance tooltip, try notification instead Tooltip may have live updates, but only until icon gets input events. Plus status icon has no even enter/leave events to craft own volume tooltip. --- ChangeLog | 1 + volwheel | 41 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index fd73011..7a3c57d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ * Optimized Alsa backend * use drop-down menu for icon channel and checkboxes list for minimixer instead of text entries * Enhanced mixer auto-detection + * Avoid hiding tooltip when possible and use desktop notification instead if available 2018-10-05 - v0.2.9 * Back from the grave, import to github diff --git a/volwheel b/volwheel index 2185dcc..d5309cf 100755 --- a/volwheel +++ b/volwheel @@ -20,6 +20,7 @@ use strict; use warnings; use Time::HiRes qw(gettimeofday tv_interval); use if (@ARGV < 1), Gtk2 => '-init'; +use Net::DBus qw(:typing); use lib '/usr/local/lib/volwheel'; use Alsa; use OSS; @@ -58,8 +59,26 @@ if (@ARGV) { exit; } +my ($bus, $serv, $dobj); + +# DBus / Desktop Notification +($bus = Net::DBus->session) && +($serv = $bus->get_service('org.freedesktop.Notifications')) && +($dobj = $serv->get_object('/org/freedesktop/Notifications', + 'org.freedesktop.Notifications')); +my %dhints = (transient => dbus_int32('1'), + synchronous => dbus_string('volume'), + value => dbus_int32(0)); + +# Tray tooltip +my $tooltip_label = Gtk2::Label->new; +my $vol_txt; +my $volume; + # Tray icon my $icon = Gtk2::StatusIcon->new; + $icon->signal_connect('query-tooltip', \&tooltip_handler); + $icon->set_has_tooltip(1); $icon->signal_connect('button_release_event', \&click_handler); $icon->signal_connect('popup-menu', \&popup); $icon->signal_connect('scroll_event', \&scroll_handler); @@ -159,6 +178,17 @@ sub launch_website { $SIG{CHLD} = "IGNORE"; } +sub tooltip_handler { + my ($icon, undef, undef, undef, $tooltip) = @_; + if ($dobj) { + notify_show(); + return 0; + } else { + $tooltip->set_custom( $tooltip_label); + return 1; + } +} + sub click_handler { my ($check, $event) = @_; @@ -209,10 +239,16 @@ sub popup { $menu->popup(undef, undef, undef, $user_data, $button, $activate_time); } +sub notify_show { + $dhints{value} = dbus_int32($volume); + return $dobj->Notify('volwheel', 1, 'multimedia-volume-control', 'volwheel', + $vol_txt, undef, \%dhints, 500); +} + sub update_icon { CORE::state $icon_number = -1; CORE::state $old_icon_num = -1; - my $volume = get_volume(); + $volume = get_volume(); if ($opt->icon_static) { $icon_number = $old_icon_num = -1; @@ -227,7 +263,8 @@ sub update_icon { } } - $icon->set_tooltip_text($opt->channel." : $volume%"); + $vol_txt = $opt->channel." : $volume%"; + $tooltip_label->set_text($vol_txt) if (! $dobj); if ($opt->show_scale) { MiniMixer::update; -- 2.44.2 From c6dd1b90c1707fa705827c0a0669997bd9b4a6fd Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Mon, 22 Jul 2024 16:52:32 +0500 Subject: [PATCH 7/8] Update TODO --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index f3575d7..af0dc51 100644 --- a/TODO +++ b/TODO @@ -1 +1,2 @@ + Add configurable mouse bindings ++ Interactive CLI tool for OSS backend like smixer (like amixer from alsa-utils) -- 2.44.2 From 986e507f0dce4c846f57e7800f9719af2a97a399 Mon Sep 17 00:00:00 2001 From: Nikita Zlobin Date: Mon, 29 Jul 2024 22:35:09 +0500 Subject: [PATCH 8/8] cleanup --- volwheel | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/volwheel b/volwheel index d5309cf..f8df221 100755 --- a/volwheel +++ b/volwheel @@ -228,7 +228,7 @@ sub popup { $item_prefs->signal_connect('activate', \&ConfDialog::show); $item_about->signal_connect('activate', \&about_dialog); - $item_quit->signal_connect('activate', \&out); + $item_quit->signal_connect('activate', \&Gtk2::main_quit); $menu->add($item_prefs); $menu->add($item_about); @@ -337,7 +337,3 @@ it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\n\n"; } - -sub out { - Gtk2->main_quit; -} -- 2.44.2