Lomiri
System.cpp
1 /*
2  * Copyright (C) 2018 The UBports project
3  * Copyright (C) 2014-2016 Canonical Ltd.
4  *
5  * This program is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 3, as published
7  * by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranties of
11  * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12  * PURPOSE. See the GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "System.h"
19 
20 #include <QDBusPendingCall>
21 #include <QDBusMessage>
22 #include <QDBusConnection>
23 #include <QDBusMetaType>
24 #include <QDir>
25 #include <QFile>
26 #include <QLocale>
27 #include <QMap>
28 #include <QProcess>
29 #include <QDebug>
30 #include <QSettings>
31 #include <QStringBuilder>
32 
33 #include <initializer_list>
34 
35 System::System()
36  : QObject()
37 {
38  // Register the argument needed for UpdateActivationEnvironment below
39  qDBusRegisterMetaType<QMap<QString,QString>>();
40 
41  if(!wizardEnabled()) {
42  m_fsWatcher.addPath(wizardEnabledPath());
43  }
44  connect(&m_fsWatcher, &QFileSystemWatcher::fileChanged, this, &System::watcherFileChanged);
45 }
46 
47 QString System::wizardEnabledPath()
48 {
49  return QDir::home().filePath(QStringLiteral(".config/lomiri/wizard-has-run"));
50 }
51 
52 QString System::currentFrameworkPath()
53 {
54  QFileInfo f("/usr/share/click/frameworks/current");
55  return f.canonicalFilePath();
56 }
57 
58 /*
59 wizardEnabled and isUpdate logic
60 
61 if wizard-has-run does NOT exist == is new install
62 if wizard-has-run exists but does NOT match current framework == is update
63 if wizard-has-run exists but does match current framework == show no wizard
64 */
65 
66 bool System::wizardPathExists() {
67  return QFile::exists(wizardEnabledPath());
68 }
69 
70 bool System::wizardEnabled() const
71 {
72  if (!wizardPathExists()) {
73  return true;
74  }
75  return isUpdate();
76 }
77 
78 QString System::readCurrentFramework()
79 {
80  QFile f(currentFrameworkPath());
81  if (!f.open(QFile::ReadOnly | QFile::Text)) return "";
82  QTextStream in(&f);
83  return in.readAll();
84 }
85 
86 QString System::readWizardEnabled()
87 {
88  QFile f(wizardEnabledPath());
89  if (!f.open(QFile::ReadOnly | QFile::Text)) return "";
90  QTextStream in(&f);
91  return in.readAll();
92 }
93 
94 QString System::version() const
95 {
96  return readCurrentFramework();
97 }
98 
99 bool System::isUpdate() const
100 {
101  if (!wizardPathExists()) {
102  return false;
103  }
104 
105  return readCurrentFramework() != readWizardEnabled();
106 }
107 
108 void System::setWizardEnabled(bool enabled)
109 {
110  if (wizardEnabled() == enabled && !isUpdate())
111  return;
112 
113  if (enabled) {
114  QFile::remove(wizardEnabledPath());
115  } else {
116  QDir(wizardEnabledPath()).mkpath(QStringLiteral(".."));
117  if (QFile::exists(wizardEnabledPath())) {
118  QFile::remove(wizardEnabledPath());
119  }
120  // For special cases check if wizardEnabledPath is a folder
121  if (QDir(wizardEnabledPath()).exists()) {
122  QDir(wizardEnabledPath()).removeRecursively();
123  }
124  if (!QFile::copy(currentFrameworkPath(), wizardEnabledPath())) {
125  // Make en empty file if framework does not exist
126  QFile f(wizardEnabledPath());
127  f.open(QFile::WriteOnly);
128  }
129  m_fsWatcher.addPath(wizardEnabledPath());
130  Q_EMIT wizardEnabledChanged();
131  Q_EMIT isUpdateChanged();
132  }
133 }
134 
135 void System::watcherFileChanged()
136 {
137  Q_EMIT wizardEnabledChanged();
138  Q_EMIT isUpdateChanged();
139  m_fsWatcher.removePath(wizardEnabledPath());
140 }
141 
142 void System::setSessionVariable(const QString &variable, const QString &value)
143 {
144  // We need to update both systemd's and DBus's environment
145  QStringList vars = { variable % QChar('=') % value };
146  QDBusMessage systemdMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
147  QStringLiteral("/org/freedesktop/systemd1"),
148  QStringLiteral("org.freedesktop.systemd1.Manager"),
149  QStringLiteral("SetEnvironment"));
150  systemdMsg << QVariant::fromValue(vars);
151  QDBusConnection::sessionBus().asyncCall(systemdMsg);
152 
153  QMap<QString,QString> valueMap;
154  valueMap.insert(variable, value);
155 
156  QDBusMessage dbusMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.DBus"),
157  QStringLiteral("/org/freedesktop/DBus"),
158  QStringLiteral("org.freedesktop.DBus"),
159  QStringLiteral("UpdateActivationEnvironment"));
160 
161  dbusMsg << QVariant::fromValue(valueMap);
162  QDBusConnection::sessionBus().asyncCall(dbusMsg);
163 }
164 
165 void System::restartUnit(const QString &unitName)
166 {
167  QDBusMessage systemdMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
168  QStringLiteral("/org/freedesktop/systemd1"),
169  QStringLiteral("org.freedesktop.systemd1.Manager"),
170  QStringLiteral("TryRestartUnit"));
171  systemdMsg << QVariant::fromValue(unitName);
172  systemdMsg << QVariant::fromValue(QStringLiteral("replace"));
173  QDBusConnection::sessionBus().asyncCall(systemdMsg);
174 }
175 
176 void System::updateSessionLocale(const QString &locale)
177 {
178  const QString language = locale.split(QStringLiteral("."))[0];
179 
180  setSessionVariable(QStringLiteral("LANGUAGE"), language);
181  setSessionVariable(QStringLiteral("LANG"), locale);
182  setSessionVariable(QStringLiteral("LC_ALL"), locale);
183 
184  // QLocale caches the default locale on startup, and Qt uses that cached
185  // copy when formatting dates. So manually update it here.
186  QLocale::setDefault(QLocale(locale));
187 
188  // Restart bits of the session to pick up new language.
189  const QStringList units {
190  "ayatana-indicators.target",
191  "lomiri-location-service-trust-stored.service",
192  "pulseaudio-trust-stored.service",
193  "sync-monitor.service",
194  "maliit-server.service",
195  "ciborium.service",
196  };
197  for (const QString& unit : units) {
198  restartUnit(unit);
199  }
200 }
201 
202 void System::skipUntilFinishedPage()
203 {
204  QSettings settings;
205  settings.setValue(QStringLiteral("Wizard/SkipUntilFinishedPage"), true);
206  settings.sync();
207 }
208 
209 QString System::distroName() const
210 {
211 #ifdef LOMIRI_DISPLAYED_DISTRO_NAME
212  return QStringLiteral(LOMIRI_DISPLAYED_DISTRO_NAME);
213 #else
214  for (const QString &fileName : {
215  QStringLiteral("/etc/os-release"),
216  QStringLiteral("/usr/lib/os-release"),
217  }) {
218  QFile file(fileName);
219  if (!file.open(QIODevice::ReadOnly))
220  continue;
221 
222  QTextStream fileIO(&file);
223  QString line;
224 
225  while (!(line = fileIO.readLine()).isEmpty()) {
226  if (line.startsWith(QStringLiteral("NAME="))) {
227  line = line.right(line.length() - 5);
228  // Remove quote, quick n' dirty way.
229  line = line.replace("\"", "");
230 
231  return line;
232  }
233  }
234  }
235 
236  return QStringLiteral("Lomiri");
237 #endif
238 }