/* * This file is part of the Zorin Taskbar extension for Zorin OS. * * 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 . */ import Gio from 'gi://Gio' import * as Pos from './panelPositions.js' const displayConfigWrapper = Gio.DBusProxy.makeProxyWrapper( ` `, ) // the module variables here are different in the settings dialog (gjs process) // and in gnome-shell (gnome-shell process) let useCache = false let cache = {} let monitorIdToIndex = {} let monitorIndexToId = {} export var displayConfigProxy = null export var availableMonitors = [] export async function init(settings) { useCache = true cache = {} await setMonitorsInfo(settings) } export function clearCache(setting) { if (setting) { cache[setting] = null return } cache = {} } /** Return object representing a settings value that is stored as JSON. */ export function getSettingsJson(settings, setting) { try { if (useCache && cache[setting]) return cache[setting] let res = JSON.parse(settings.get_string(setting)) cache[setting] = res return res } catch (e) { console.log('Error parsing positions: ' + e.message) } } /** Write value object as JSON to setting in settings. */ export function setSettingsJson(settings, setting, value) { try { const json = JSON.stringify(value) settings.set_string(setting, json) cache[setting] = value } catch (e) { console.log('Error serializing setting: ' + e.message) } } // Previously, the monitor index was used as an id to persist per monitor // settings. Since these indexes are unreliable AF, switch to use the monitor // serial as its id while keeping it backward compatible. function getMonitorSetting(settings, settingName, monitorIndex, fallback) { let monitorId = monitorIndexToId[monitorIndex] settings = getSettingsJson(settings, settingName) return ( settings[monitorId] || settings[monitorIndex] || settings[availableMonitors[monitorIndex]?.id] || fallback ) } function setMonitorSetting(settings, settingName, monitorIndex, value) { let monitorId = monitorIndexToId[monitorIndex] let usedId = monitorId || monitorIndex let currentSettings = getSettingsJson(settings, settingName) if (monitorId) delete currentSettings[monitorIndex] currentSettings[usedId] = value setSettingsJson(settings, settingName, currentSettings) } /** Returns size of panel on a specific monitor, in pixels. */ export function getPanelSize(settings, monitorIndex) { // Pull in deprecated setting if panel-sizes does not have setting for monitor. return getMonitorSetting( settings, 'panel-sizes', monitorIndex, settings.get_int('panel-size') || 48, ) } export function setPanelSize(settings, monitorIndex, value) { if (!(Number.isInteger(value) && value <= 128 && value >= 16)) { console.log('Not setting invalid panel size: ' + value) return } setMonitorSetting(settings, 'panel-sizes', monitorIndex, value) } /** * Returns length of panel on a specific monitor, as a whole number percent, * from settings. e.g. 100, or -1 for a dynamic panel length */ export function getPanelLength(settings, monitorIndex) { return getMonitorSetting(settings, 'panel-lengths', monitorIndex, 100) } export function setPanelLength(settings, monitorIndex, value) { if ( !(Number.isInteger(value) && ((value <= 100 && value >= 20) || value == -1)) ) { console.log('Not setting invalid panel length: ' + value, new Error().stack) return } setMonitorSetting(settings, 'panel-lengths', monitorIndex, value) } /** Returns position of panel on a specific monitor. */ export function getPanelPosition(settings, monitorIndex) { return getMonitorSetting( settings, 'panel-positions', monitorIndex, settings.get_string('panel-position') || Pos.BOTTOM, ) } export function setPanelPosition(settings, monitorIndex, value) { if ( !( value === Pos.TOP || value === Pos.BOTTOM || value === Pos.LEFT || value === Pos.RIGHT ) ) { console.log('Not setting invalid panel position: ' + value) return } setMonitorSetting(settings, 'panel-positions', monitorIndex, value) } /** Returns anchor location of panel on a specific monitor. */ export function getPanelAnchor(settings, monitorIndex) { return getMonitorSetting(settings, 'panel-anchors', monitorIndex, Pos.MIDDLE) } export function setPanelAnchor(settings, monitorIndex, value) { if (!(value === Pos.START || value === Pos.MIDDLE || value === Pos.END)) { console.log('Not setting invalid panel anchor: ' + value) return } setMonitorSetting(settings, 'panel-anchors', monitorIndex, value) } export function getPanelElementPositions(settings, monitorIndex) { return getMonitorSetting( settings, 'panel-element-positions', monitorIndex, Pos.defaults, ) } export function setPanelElementPositions(settings, monitorIndex, value) { setMonitorSetting(settings, 'panel-element-positions', monitorIndex, value) } export function setMonitorsInfo(settings) { return new Promise((resolve, reject) => { try { let monitorInfos = [] let saveMonitorState = (proxy) => { proxy.GetCurrentStateRemote((displayInfo, e) => { if (e) return reject(`Error getting display state: ${e}`) let ids = {} //https://gitlab.gnome.org/GNOME/mutter/-/blob/main/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml#L347 displayInfo[2].forEach((logicalMonitor, i) => { let [connector, vendor, product, serial] = logicalMonitor[5][0] let id = i let primary = logicalMonitor[4] // if by any chance 2 monitors have the same id, use the connector string // instead, which should be unique but varies between x11 and wayland :( // worst case scenario, resort to using the dumbass index if (vendor && serial) id = `${vendor}-${serial}` if (ids[id]) id = connector && !ids[connector] ? connector : i monitorInfos.push({ id, product, primary, }) monitorIdToIndex[id] = i monitorIndexToId[i] = id ids[id] = 1 }) _saveMonitors(settings, monitorInfos) resolve() }) } if (!displayConfigProxy) displayConfigProxy = new displayConfigWrapper( Gio.DBus.session, 'org.gnome.Mutter.DisplayConfig', '/org/gnome/Mutter/DisplayConfig', (proxy, e) => { if (e) return reject(`Error creating display proxy: ${e}`) saveMonitorState(proxy) }, ) else saveMonitorState(displayConfigProxy) } catch (e) { reject(e) } }) } function _saveMonitors(settings, monitorInfos) { /* Commented out as Zorin Taskbar always uses gnome-shell primary monitor let keyPrimary = 'primary-monitor' let dtpPrimaryMonitor = settings.get_string(keyPrimary) // convert previously saved index to monitor id if (dtpPrimaryMonitor.match(/^\d{1,2}$/) && monitorInfos[dtpPrimaryMonitor]) settings.set_string(keyPrimary, monitorInfos[dtpPrimaryMonitor].id) */ availableMonitors = Object.freeze(monitorInfos) } // this is for backward compatibility, to remove in a few versions export function adjustMonitorSettings(settings) { let updateSettings = (settingName) => { let monitorSettings = getSettingsJson(settings, settingName) let updatedSettings = {} Object.keys(monitorSettings).forEach((key) => { let initialKey = key if (key.match(/^\d{1,2}$/)) key = monitorIndexToId[key] || key updatedSettings[key] = monitorSettings[initialKey] }) setSettingsJson(settings, settingName, updatedSettings) } updateSettings('panel-sizes') updateSettings('panel-lengths') updateSettings('panel-positions') updateSettings('panel-anchors') updateSettings('panel-element-positions') }