I watch a lot of videos with various refresh rates, so I’m trying to write an extension so I can quickly change the display refresh rate from a panel icon instead of all the extra clicks of going through system settings. I need it to use a persistent popover instead of an ordinary menu because my TV enables overscan in 50Hz modes, making the panel invisible, and the TV doesn’t have an option to disable that AFAICT.
The docs encourage the use of GMenuModel
, hence Gtk.Popover.new_from_model()
, but it doesn’t seem to actually work. The model should have a Close button in its own unlabeled section and a labeled section for each physical display containing radio buttons for each refresh rate. Instead, it creates all the items as inert labels. As this will be an extension it doesn’t seem appropriate to use the “app” or “win” action groups, so I’ve created an action group with prefix “refreshswitch” and attached it to the parent button that I’m passing as the relative_to
parameter of the constructor, using Gtk.Widget.insert_action_group()
so it should be able to read the correct button roles from that, right?
At the moment I’m using a test window containing a Gtk.MenuButton
to make it easier to test the popover. A second problem is that the position of the popover is always to the left and above the button, even if it has to open almost entirely off the edges of the screen. Gtk.Popover.set_position()
and Gtk.MenuButton.set_direction()
(the arrow is pointing downwards anyway) have no effect on this.
Here are my functions for creating the action group and menu model:
function buildActionGroup(displays) {
actionGroup = new Gio.SimpleActionGroup();
const action = Gio.SimpleAction.new("close", null);
action.connect("activate", () => {
// TODO: Close the popover
log("Close action activated");
});
const vt = GLib.VariantType.new('s');
for (const dn in displays) {
const disp = displays[dn];
const states = disp.modes.map((_, i) =>
GLib.Variant.new_string(`${i}`));
const action = Gio.SimpleAction.new_stateful(`disp_${dn}`,
null, states[disp.current_mode]);
action.set_state_hint(GLib.Variant.new_array(vt, states));
action.connect("change-state", (_, value) => {
// TODO: Check if mode has really changed and switch it
log(`Action disp_${dn} changed state to ${value.get_string()}`);
});
actionGroup.add_action(action);
}
if (parentWidget) {
parentWidget.insert_action_group("refreshswitch", actionGroup);
}
}
function buildMenuModel(displays) {
menuModel = new Gio.Menu();
let section = new Gio.Menu();
section.append_item(Gio.MenuItem.new("Close", "refreshswitch.close"));
menuModel.append_section(null, section);
for (const dn in displays) {
const disp = displays[dn];
section = new Gio.Menu();
for (const rn in disp.refresh) {
section.append_item(Gio.MenuItem.new(`${disp.refresh[rn]}Hz`,
`refreshswitch.disp_${dn}::${rn}`));
}
menuModel.append_section(disp.name, section);
}
}