Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
ximper-shell-osd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
ximper-shell-osd
Commits
c2222d21
Verified
Commit
c2222d21
authored
Mar 28, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
brightness: add sysfs and DDC/CI monitor support
parent
6788b189
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
396 additions
and
7 deletions
+396
-7
meson_options.txt
meson_options.txt
+1
-0
application.vala
src/application.vala
+36
-2
config.vala
src/config.vala
+9
-0
dbus-service.vala
src/dbus-service.vala
+4
-0
meson.build
src/meson.build
+16
-0
backlight-monitor.vala
src/monitors/backlight-monitor.vala
+92
-0
ddc-monitor.vala
src/monitors/ddc-monitor.vala
+114
-0
ddcutil.vapi
src/monitors/ddcutil.vapi
+92
-0
pulse-monitor.vala
src/monitors/pulse-monitor.vala
+8
-0
osd-content.vala
src/osd-content.vala
+12
-5
osd-window.vala
src/osd-window.vala
+12
-0
No files found.
meson_options.txt
View file @
c2222d21
option('pulse-audio', type: 'boolean', value: true, description: 'Monitor PulseAudio volume changes.')
option('pulse-audio', type: 'boolean', value: true, description: 'Monitor PulseAudio volume changes.')
option('ddc', type: 'boolean', value: true, description: 'Monitor DDC/CI brightness via libddcutil.')
src/application.vala
View file @
c2222d21
...
@@ -17,6 +17,10 @@ namespace XimperShellOsd {
...
@@ -17,6 +17,10 @@ namespace XimperShellOsd {
#if HAVE_PULSE_AUDIO
#if HAVE_PULSE_AUDIO
private
PulseMonitor
?
pulse_monitor
;
private
PulseMonitor
?
pulse_monitor
;
#endif
#endif
private
BacklightMonitor
?
backlight_monitor
;
#if HAVE_DDC
private
DdcMonitor
?
ddc_monitor
;
#endif
public
Application
()
{
public
Application
()
{
Object
(
Object
(
...
@@ -54,14 +58,34 @@ namespace XimperShellOsd {
...
@@ -54,14 +58,34 @@ namespace XimperShellOsd {
if
(
is_muted
)
{
if
(
is_muted
)
{
osd_window
.
show_osd
(
OsdType
.
VOLUME_MUTE
,
0
,
"Muted"
);
osd_window
.
show_osd
(
OsdType
.
VOLUME_MUTE
,
0
,
"Muted"
);
}
else
{
}
else
{
var
icon
=
get_volume_icon
(
percent
);
osd_window
.
show_osd
(
OsdType
.
VOLUME
,
percent
/
100.0
,
"%d%%"
.
printf
((
int
)
percent
),
get_volume_icon
(
percent
));
osd_window
.
show_osd
(
OsdType
.
VOLUME
,
percent
/
100.0
,
"%d%%"
.
printf
((
int
)
percent
),
icon
);
}
}
});
});
pulse_monitor
.
start
();
pulse_monitor
.
start
();
}
}
#endif
#endif
if
(
config
.
monitor_brightness
)
{
var
device
=
BacklightMonitor
.
detect_device
();
if
(
device
!=
null
)
{
backlight_monitor
=
new
BacklightMonitor
(
device
);
backlight_monitor
.
brightness_changed
.
connect
((
percent
)
=>
{
osd_window
.
show_osd
(
OsdType
.
BRIGHTNESS
,
percent
/
100.0
,
"%d%%"
.
printf
(
percent
));
});
backlight_monitor
.
start
();
}
}
#if HAVE_DDC
if
(
config
.
monitor_ddc
)
{
ddc_monitor
=
new
DdcMonitor
(
config
.
ddc_poll_interval_sec
);
ddc_monitor
.
brightness_changed
.
connect
((
percent
)
=>
{
osd_window
.
show_osd
(
OsdType
.
BRIGHTNESS
,
percent
/
100.0
,
"%d%%"
.
printf
(
percent
));
});
ddc_monitor
.
start
();
}
#endif
hold
();
hold
();
}
}
...
@@ -78,6 +102,16 @@ namespace XimperShellOsd {
...
@@ -78,6 +102,16 @@ namespace XimperShellOsd {
pulse_monitor
=
null
;
pulse_monitor
=
null
;
}
}
#endif
#endif
if
(
backlight_monitor
!=
null
)
{
backlight_monitor
.
close
();
backlight_monitor
=
null
;
}
#if HAVE_DDC
if
(
ddc_monitor
!=
null
)
{
ddc_monitor
.
close
();
ddc_monitor
=
null
;
}
#endif
base
.
shutdown
();
base
.
shutdown
();
}
}
...
...
src/config.vala
View file @
c2222d21
...
@@ -6,6 +6,9 @@ namespace XimperShellOsd {
...
@@ -6,6 +6,9 @@ namespace XimperShellOsd {
public
int
margin_bottom
{
get
;
set
;
default
=
80
;
}
public
int
margin_bottom
{
get
;
set
;
default
=
80
;
}
public
int
width
{
get
;
set
;
default
=
300
;
}
public
int
width
{
get
;
set
;
default
=
300
;
}
public
bool
monitor_volume
{
get
;
set
;
default
=
true
;
}
public
bool
monitor_volume
{
get
;
set
;
default
=
true
;
}
public
bool
monitor_brightness
{
get
;
set
;
default
=
true
;
}
public
bool
monitor_ddc
{
get
;
set
;
default
=
true
;
}
public
int
ddc_poll_interval_sec
{
get
;
set
;
default
=
5
;
}
private
string
config_path
;
private
string
config_path
;
...
@@ -34,6 +37,12 @@ namespace XimperShellOsd {
...
@@ -34,6 +37,12 @@ namespace XimperShellOsd {
width
=
(
int
)
root
.
get_int_member
(
"width"
);
width
=
(
int
)
root
.
get_int_member
(
"width"
);
if
(
root
.
has_member
(
"monitor_volume"
))
if
(
root
.
has_member
(
"monitor_volume"
))
monitor_volume
=
root
.
get_boolean_member
(
"monitor_volume"
);
monitor_volume
=
root
.
get_boolean_member
(
"monitor_volume"
);
if
(
root
.
has_member
(
"monitor_brightness"
))
monitor_brightness
=
root
.
get_boolean_member
(
"monitor_brightness"
);
if
(
root
.
has_member
(
"monitor_ddc"
))
monitor_ddc
=
root
.
get_boolean_member
(
"monitor_ddc"
);
if
(
root
.
has_member
(
"ddc_poll_interval_sec"
))
ddc_poll_interval_sec
=
(
int
)
root
.
get_int_member
(
"ddc_poll_interval_sec"
);
}
catch
(
Error
e
)
{
}
catch
(
Error
e
)
{
warning
(
"Failed to load OSD config: %s"
,
e
.
message
);
warning
(
"Failed to load OSD config: %s"
,
e
.
message
);
}
}
...
...
src/dbus-service.vala
View file @
c2222d21
...
@@ -20,5 +20,9 @@ namespace XimperShellOsd {
...
@@ -20,5 +20,9 @@ namespace XimperShellOsd {
);
);
}
}
}
}
public
void
show_brightness_osd
(
double
percent
)
throws
DBusError
,
IOError
{
app
.
show_osd_external
(
OsdType
.
BRIGHTNESS
,
percent
/
100.0
,
"%d%%"
.
printf
((
int
)
percent
));
}
}
}
}
}
src/meson.build
View file @
c2222d21
cc = meson.get_compiler('c')
osd_deps = [
osd_deps = [
dependency('gtk4', version: '>= 4.12'),
dependency('gtk4', version: '>= 4.12'),
dependency('libadwaita-1', version: '>= 1.4'),
dependency('libadwaita-1', version: '>= 1.4'),
dependency('gtk4-layer-shell-0', version: '>= 1.0'),
dependency('gtk4-layer-shell-0', version: '>= 1.0'),
dependency('json-glib-1.0'),
dependency('json-glib-1.0'),
dependency('gio-unix-2.0'),
dependency('gio-unix-2.0'),
cc.find_library('m', required: false),
]
]
osd_sources = files(
osd_sources = files(
...
@@ -13,6 +16,7 @@ osd_sources = files(
...
@@ -13,6 +16,7 @@ osd_sources = files(
'osd-window.vala',
'osd-window.vala',
'osd-content.vala',
'osd-content.vala',
'dbus-service.vala',
'dbus-service.vala',
'monitors/backlight-monitor.vala',
)
)
if get_option('pulse-audio')
if get_option('pulse-audio')
...
@@ -26,6 +30,18 @@ if get_option('pulse-audio')
...
@@ -26,6 +30,18 @@ if get_option('pulse-audio')
)
)
endif
endif
if get_option('ddc')
add_project_arguments('-D', 'HAVE_DDC', language: 'vala')
ddcutil_vapi_dir = meson.current_source_dir() / 'monitors'
osd_deps += [
cc.find_library('ddcutil', required: true),
meson.get_compiler('vala').find_library('ddcutil', dirs: ddcutil_vapi_dir),
]
osd_sources += files(
'monitors/ddc-monitor.vala',
)
endif
executable('ximper-shell-osd', osd_sources,
executable('ximper-shell-osd', osd_sources,
dependencies: osd_deps,
dependencies: osd_deps,
c_args: ['-w'],
c_args: ['-w'],
...
...
src/monitors/backlight-monitor.vala
0 → 100644
View file @
c2222d21
namespace
XimperShellOsd
{
public
class
BacklightMonitor
:
Object
{
private
string
path_current
;
private
string
path_max
;
private
File
fd
;
private
FileMonitor
?
monitor
;
private
int
max_value
;
private
int
last_percent
=
-
1
;
public
signal
void
brightness_changed
(
int
percent
);
public
BacklightMonitor
(
string
device
)
{
path_current
=
Path
.
build_filename
(
"/sys"
,
"class"
,
"backlight"
,
device
,
"brightness"
);
path_max
=
Path
.
build_filename
(
"/sys"
,
"class"
,
"backlight"
,
device
,
"max_brightness"
);
fd
=
File
.
new_for_path
(
path_current
);
}
public
bool
start
()
{
if
(!
fd
.
query_exists
())
{
warning
(
"Backlight device not found: %s"
,
path_current
);
return
false
;
}
read_max
();
read_brightness_silent
();
try
{
monitor
=
fd
.
monitor
(
FileMonitorFlags
.
NONE
,
null
);
monitor
.
changed
.
connect
(()
=>
read_brightness
());
}
catch
(
Error
e
)
{
warning
(
"Failed to monitor backlight: %s"
,
e
.
message
);
return
false
;
}
return
true
;
}
public
void
close
()
{
if
(
monitor
!=
null
)
{
monitor
.
cancel
();
monitor
=
null
;
}
}
private
void
read_max
()
{
try
{
var
dis
=
new
DataInputStream
(
File
.
new_for_path
(
path_max
).
read
(
null
));
max_value
=
int
.
parse
(
dis
.
read_line
(
null
));
}
catch
(
Error
e
)
{
warning
(
"Failed to read max brightness: %s"
,
e
.
message
);
}
}
private
void
read_brightness_silent
()
{
try
{
var
dis
=
new
DataInputStream
(
fd
.
read
(
null
));
int
val
=
int
.
parse
(
dis
.
read_line
(
null
));
last_percent
=
(
int
)
Math
.
round
(
val
*
100.0
/
max_value
);
}
catch
(
Error
e
)
{
warning
(
"Failed to read brightness: %s"
,
e
.
message
);
}
}
private
void
read_brightness
()
{
try
{
var
dis
=
new
DataInputStream
(
fd
.
read
(
null
));
int
val
=
int
.
parse
(
dis
.
read_line
(
null
));
int
percent
=
(
int
)
Math
.
round
(
val
*
100.0
/
max_value
);
if
(
percent
!=
last_percent
)
{
last_percent
=
percent
;
brightness_changed
(
percent
);
}
}
catch
(
Error
e
)
{
warning
(
"Failed to read brightness: %s"
,
e
.
message
);
}
}
public
static
string
?
detect_device
()
{
try
{
var
dir
=
Dir
.
open
(
"/sys/class/backlight"
);
string
?
name
;
while
((
name
=
dir
.
read_name
())
!=
null
)
{
return
name
;
}
}
catch
(
FileError
e
)
{}
return
null
;
}
}
}
src/monitors/ddc-monitor.vala
0 → 100644
View file @
c2222d21
namespace
XimperShellOsd
{
public
class
DdcMonitor
:
Object
{
private
void
*
handle
=
null
;
private
uint
poll_source
=
0
;
private
int
last_percent
=
-
1
;
private
int
poll_interval
;
private
static
bool
lib_initialized
=
false
;
public
signal
void
brightness_changed
(
int
percent
);
public
DdcMonitor
(
int
poll_interval_sec
=
5
)
{
this
.
poll_interval
=
poll_interval_sec
;
}
public
bool
start
()
{
if
(!
init_library
())
return
false
;
if
(!
open_display
())
return
false
;
read_brightness_silent
();
poll_source
=
Timeout
.
add_seconds
(
poll_interval
,
()
=>
{
read_brightness
();
return
Source
.
CONTINUE
;
});
return
true
;
}
public
void
close
()
{
if
(
poll_source
!=
0
)
{
Source
.
remove
(
poll_source
);
poll_source
=
0
;
}
}
~
DdcMonitor
()
{
close
();
if
(
handle
!=
null
)
{
Ddcutil
.
close_display
(
handle
);
handle
=
null
;
}
}
private
static
bool
init_library
()
{
if
(
lib_initialized
)
return
true
;
string
[]?
msgs
;
int
rc
=
Ddcutil
.
init2
(
null
,
Ddcutil
.
SyslogLevel
.
NEVER
,
Ddcutil
.
InitOptions
.
DISABLE_CONFIG_FILE
,
out
msgs
);
if
(
rc
!=
0
)
{
warning
(
"ddcutil init failed: %d"
,
rc
);
return
false
;
}
lib_initialized
=
true
;
return
true
;
}
private
bool
open_display
()
{
Ddcutil
.
DisplayInfoList
?
dlist
;
int
rc
=
Ddcutil
.
get_display_info_list2
(
false
,
out
dlist
);
if
(
rc
!=
0
||
dlist
==
null
||
dlist
.
ct
==
0
)
{
warning
(
"No DDC displays found"
);
return
false
;
}
void
*
dh
;
rc
=
Ddcutil
.
open_display2
(
dlist
.
info
[
0
].
dref
,
true
,
out
dh
);
if
(
rc
!=
0
)
{
warning
(
"Failed to open DDC display: %d"
,
rc
);
return
false
;
}
handle
=
dh
;
return
true
;
}
private
void
read_brightness_silent
()
{
if
(
handle
==
null
)
return
;
Ddcutil
.
NonTableVcpValue
val
;
int
rc
=
Ddcutil
.
get_non_table_vcp_value
(
handle
,
0x10
,
out
val
);
if
(
rc
!=
0
)
return
;
int
current
=
(
val
.
sh
<<
8
)
|
val
.
sl
;
int
max
=
(
val
.
mh
<<
8
)
|
val
.
ml
;
if
(
max
>
0
)
{
last_percent
=
(
int
)
Math
.
round
(
current
*
100.0
/
max
);
}
}
private
void
read_brightness
()
{
if
(
handle
==
null
)
return
;
Ddcutil
.
NonTableVcpValue
val
;
int
rc
=
Ddcutil
.
get_non_table_vcp_value
(
handle
,
0x10
,
out
val
);
if
(
rc
!=
0
)
return
;
int
current
=
(
val
.
sh
<<
8
)
|
val
.
sl
;
int
max
=
(
val
.
mh
<<
8
)
|
val
.
ml
;
if
(
max
==
0
)
return
;
int
percent
=
(
int
)
Math
.
round
(
current
*
100.0
/
max
);
if
(
percent
!=
last_percent
)
{
last_percent
=
percent
;
brightness_changed
(
percent
);
}
}
}
}
src/monitors/ddcutil.vapi
0 → 100644
View file @
c2222d21
[CCode (cheader_filename = "ddcutil_c_api.h")]
namespace Ddcutil {
[CCode (cname = "DDCA_Syslog_Level", cprefix = "DDCA_SYSLOG_")]
public enum SyslogLevel {
[CCode (cname = "DDCA_SYSLOG_NOT_SET")]
NOT_SET,
[CCode (cname = "DDCA_SYSLOG_NEVER")]
NEVER
}
[CCode (cname = "DDCA_Init_Options", cprefix = "DDCA_INIT_OPTIONS_")]
[Flags]
public enum InitOptions {
[CCode (cname = "DDCA_INIT_OPTIONS_NONE")]
NONE,
[CCode (cname = "DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE")]
DISABLE_CONFIG_FILE
}
[CCode (cname = "DDCA_Non_Table_Vcp_Value", has_type_id = false)]
public struct NonTableVcpValue {
public uint8 mh;
public uint8 ml;
public uint8 sh;
public uint8 sl;
}
// All fields must be present to match C struct layout
[CCode (cname = "DDCA_Display_Info", has_type_id = false, destroy_function = "")]
public struct DisplayInfo {
public char marker[4];
public int dispno;
[CCode (cname = "path")]
public uint8 _path[8];
public int usb_bus;
public int usb_device;
public char mfg_id[4];
public char model_name[14];
public char sn[14];
public uint16 product_code;
public uint8 edid_bytes[128];
[CCode (cname = "vcp_version")]
public uint8 _vcp_version[2];
[CCode (cname = "dref")]
public void* dref;
}
[CCode (cname = "DDCA_Display_Info_List", has_type_id = false,
free_function = "ddca_free_display_info_list")]
[Compact]
public class DisplayInfoList {
public int ct;
[CCode (cname = "info", array_length_cname = "ct")]
public DisplayInfo info[0];
}
[CCode (cname = "ddca_init2")]
public static int init2 (
string? libopts,
SyslogLevel syslog_level,
InitOptions opts,
[CCode (array_length = false)]
out string[]? infomsg);
[CCode (cname = "ddca_get_display_info_list2")]
public static int get_display_info_list2 (
bool include_invalid,
out DisplayInfoList? dlist);
[CCode (cname = "ddca_open_display2")]
public static int open_display2 (
void* dref,
bool wait,
out void* dh);
[CCode (cname = "ddca_close_display")]
public static int close_display (void* dh);
[CCode (cname = "ddca_get_non_table_vcp_value")]
public static int get_non_table_vcp_value (
void* dh,
uint8 feature_code,
out NonTableVcpValue valrec);
[CCode (cname = "ddca_set_non_table_vcp_value")]
public static int set_non_table_vcp_value (
void* dh,
uint8 feature_code,
uint8 hi_byte,
uint8 lo_byte);
}
src/monitors/pulse-monitor.vala
View file @
c2222d21
...
@@ -10,6 +10,7 @@ namespace XimperShellOsd {
...
@@ -10,6 +10,7 @@ namespace XimperShellOsd {
private
string
?
default_sink_name
;
private
string
?
default_sink_name
;
private
double
last_volume
=
-
1
;
private
double
last_volume
=
-
1
;
private
bool
last_muted
=
false
;
private
bool
last_muted
=
false
;
private
bool
initialized
=
false
;
public
signal
void
volume_changed
(
double
percent
,
bool
is_muted
);
public
signal
void
volume_changed
(
double
percent
,
bool
is_muted
);
...
@@ -100,6 +101,13 @@ namespace XimperShellOsd {
...
@@ -100,6 +101,13 @@ namespace XimperShellOsd {
bool
is_muted
=
info
.
mute
==
1
;
bool
is_muted
=
info
.
mute
==
1
;
double
volume
=
volume_to_double
(
info
.
volume
.
max
());
double
volume
=
volume_to_double
(
info
.
volume
.
max
());
if
(!
initialized
)
{
last_volume
=
volume
;
last_muted
=
is_muted
;
initialized
=
true
;
return
;
}
if
(
volume
!=
last_volume
||
is_muted
!=
last_muted
)
{
if
(
volume
!=
last_volume
||
is_muted
!=
last_muted
)
{
last_volume
=
volume
;
last_volume
=
volume
;
last_muted
=
is_muted
;
last_muted
=
is_muted
;
...
...
src/osd-content.vala
View file @
c2222d21
...
@@ -2,7 +2,8 @@ namespace XimperShellOsd {
...
@@ -2,7 +2,8 @@ namespace XimperShellOsd {
public
enum
OsdType
{
public
enum
OsdType
{
VOLUME
,
VOLUME
,
VOLUME_MUTE
;
VOLUME_MUTE
,
BRIGHTNESS
;
}
}
public
class
OsdContent
:
Gtk
.
Box
{
public
class
OsdContent
:
Gtk
.
Box
{
...
@@ -40,10 +41,16 @@ namespace XimperShellOsd {
...
@@ -40,10 +41,16 @@ namespace XimperShellOsd {
}
}
public
void
update
(
OsdType
type
,
double
value
,
string
text
,
string
?
icon_name
=
null
)
{
public
void
update
(
OsdType
type
,
double
value
,
string
text
,
string
?
icon_name
=
null
)
{
if
(
type
==
OsdType
.
VOLUME_MUTE
)
{
switch
(
type
)
{
icon
.
icon_name
=
"audio-volume-muted-symbolic"
;
case
OsdType
.
VOLUME_MUTE
:
}
else
{
icon
.
icon_name
=
"audio-volume-muted-symbolic"
;
icon
.
icon_name
=
icon_name
??
"audio-volume-high-symbolic"
;
break
;
case
OsdType
.
BRIGHTNESS
:
icon
.
icon_name
=
icon_name
??
"display-brightness-symbolic"
;
break
;
default
:
icon
.
icon_name
=
icon_name
??
"audio-volume-high-symbolic"
;
break
;
}
}
progress
.
fraction
=
value
.
clamp
(
0.0
,
1.0
);
progress
.
fraction
=
value
.
clamp
(
0.0
,
1.0
);
...
...
src/osd-window.vala
View file @
c2222d21
...
@@ -8,6 +8,10 @@ namespace XimperShellOsd {
...
@@ -8,6 +8,10 @@ namespace XimperShellOsd {
private
Adw
.
TimedAnimation
?
fade_in_anim
;
private
Adw
.
TimedAnimation
?
fade_in_anim
;
private
Adw
.
TimedAnimation
?
fade_out_anim
;
private
Adw
.
TimedAnimation
?
fade_out_anim
;
private
bool
is_showing
=
false
;
private
bool
is_showing
=
false
;
private
OsdType
last_type
;
private
double
last_value
=
-
1
;
private
int64
last_show_time
=
0
;
private
const
int64
DEDUP_WINDOW_US
=
500000
;
public
OsdWindow
(
Application
app
)
{
public
OsdWindow
(
Application
app
)
{
this
.
app
=
app
;
this
.
app
=
app
;
...
@@ -34,6 +38,14 @@ namespace XimperShellOsd {
...
@@ -34,6 +38,14 @@ namespace XimperShellOsd {
}
}
public
void
show_osd
(
OsdType
type
,
double
value
,
string
label
,
string
?
icon_name
=
null
)
{
public
void
show_osd
(
OsdType
type
,
double
value
,
string
label
,
string
?
icon_name
=
null
)
{
int64
now
=
GLib
.
get_monotonic_time
();
if
(
type
==
last_type
&&
value
==
last_value
&&
(
now
-
last_show_time
)
<
DEDUP_WINDOW_US
)
{
return
;
}
last_type
=
type
;
last_value
=
value
;
last_show_time
=
now
;
content
.
update
(
type
,
value
,
label
,
icon_name
);
content
.
update
(
type
,
value
,
label
,
icon_name
);
cancel_hide
();
cancel_hide
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment