Compare commits
7 Commits
3.35.92
...
155-move-f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
13ddf1eb83 | ||
![]() |
15ab33a11a | ||
![]() |
5e04f6eb23 | ||
![]() |
0dd171a7c8 | ||
![]() |
837fbbf417 | ||
![]() |
d7b61e7281 | ||
![]() |
073da0806c |
@@ -1,12 +1,19 @@
|
||||
<node>
|
||||
<interface name="org.gnome.Shell.CalendarServer">
|
||||
<method name="GetEvents">
|
||||
<arg type="x" direction="in" />
|
||||
<arg type="x" direction="in" />
|
||||
<arg type="b" direction="in" />
|
||||
<arg type="a(sssbxxa{sv})" direction="out" />
|
||||
<method name="SetTimeRange">
|
||||
<arg type="x" name="since" direction="in"/>
|
||||
<arg type="x" name="until" direction="in"/>
|
||||
<arg type="b" name="force_reload" direction="in"/>
|
||||
</method>
|
||||
<signal name="EventsAdded">
|
||||
<arg type="a(ssbxxa{sv})" name="events" direction="out"/>
|
||||
</signal>
|
||||
<signal name="EventsRemoved">
|
||||
<arg type="as" name="ids" direction="out"/>
|
||||
</signal>
|
||||
<signal name="ClientDisappeared">
|
||||
<arg type="s" name="source_uid" direction="out"/>
|
||||
</signal>
|
||||
<property name="HasCalendars" type="b" access="read" />
|
||||
<signal name="Changed" />
|
||||
</interface>
|
||||
</node>
|
||||
|
@@ -1,5 +1,6 @@
|
||||
desktop_files = [
|
||||
'org.gnome.Shell.desktop',
|
||||
'org.gnome.Shell.CalendarServer.desktop',
|
||||
'org.gnome.Extensions.desktop',
|
||||
]
|
||||
service_files = []
|
||||
@@ -13,6 +14,7 @@ desktopconf = configuration_data()
|
||||
# We substitute in bindir so it works as an autostart
|
||||
# file when built in a non-system prefix
|
||||
desktopconf.set('bindir', bindir)
|
||||
desktopconf.set('libexecdir', libexecdir)
|
||||
desktopconf.set('systemd_hidden', have_systemd ? 'true' : 'false')
|
||||
|
||||
foreach desktop_file : desktop_files
|
||||
|
9
data/org.gnome.Shell.CalendarServer.desktop.in.in
Normal file
9
data/org.gnome.Shell.CalendarServer.desktop.in.in
Normal file
@@ -0,0 +1,9 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Clock Applet
|
||||
Icon=appointment-soon
|
||||
Exec=@libexecdir@/gnome-shell-calendar-server
|
||||
Terminal=false
|
||||
Categories=
|
||||
OnlyShowIn=GNOME
|
||||
NoDisplay=true
|
@@ -220,7 +220,9 @@ class DBusEventSource extends EventSourceBase {
|
||||
}
|
||||
}
|
||||
|
||||
this._dbusProxy.connectSignal('Changed', this._onChanged.bind(this));
|
||||
this._dbusProxy.connectSignal('EventsAdded', this._onEventsAdded.bind(this));
|
||||
this._dbusProxy.connectSignal('EventsRemoved', this._onEventsRemoved.bind(this));
|
||||
this._dbusProxy.connectSignal('ClientDisappeared', this._onClientDisappeared.bind(this));
|
||||
|
||||
this._dbusProxy.connect('notify::g-name-owner', () => {
|
||||
if (this._dbusProxy.g_name_owner)
|
||||
@@ -257,7 +259,7 @@ class DBusEventSource extends EventSourceBase {
|
||||
}
|
||||
|
||||
_resetCache() {
|
||||
this._events = [];
|
||||
this._events = new Map();
|
||||
this._lastRequestBegin = null;
|
||||
this._lastRequestEnd = null;
|
||||
}
|
||||
@@ -273,28 +275,59 @@ class DBusEventSource extends EventSourceBase {
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
_onChanged() {
|
||||
this._loadEvents(false);
|
||||
}
|
||||
_onEventsAdded(dbusProxy, nameOwner, argArray) {
|
||||
let appointments = argArray[0] || [];
|
||||
let changed = false;
|
||||
|
||||
_onEventsReceived(results, _error) {
|
||||
let newEvents = [];
|
||||
let appointments = results[0] || [];
|
||||
for (let n = 0; n < appointments.length; n++) {
|
||||
let a = appointments[n];
|
||||
let date = new Date(a[4] * 1000);
|
||||
let end = new Date(a[5] * 1000);
|
||||
let id = a[0];
|
||||
let summary = a[1];
|
||||
let allDay = a[3];
|
||||
let allDay = a[2];
|
||||
let date = new Date(a[3] * 1000);
|
||||
let end = new Date(a[4] * 1000);
|
||||
let event = new CalendarEvent(id, date, end, summary, allDay);
|
||||
newEvents.push(event);
|
||||
}
|
||||
newEvents.sort((ev1, ev2) => ev1.date.getTime() - ev2.date.getTime());
|
||||
this._events.set(event.id, event);
|
||||
|
||||
this._events = newEvents;
|
||||
this._isLoading = false;
|
||||
this.emit('changed');
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
_onEventsRemoved(dbusProxy, nameOwner, argArray) {
|
||||
let ids = argArray[0] || [];
|
||||
let changed = false;
|
||||
|
||||
for (let n = 0; n < ids.length; n++) {
|
||||
let id = ids[n];
|
||||
|
||||
if (this._events.delete(id))
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
_onClientDisappeared(dbusProxy, nameOwner, argArray) {
|
||||
let sourceUid = argArray[0] || "";
|
||||
let changed = false;
|
||||
let idsIter = this._events.keys();
|
||||
|
||||
sourceUid += '\n';
|
||||
|
||||
for (let item = idsIter.next(); !item.done; item = idsIter.next()) {
|
||||
let id = item.value;
|
||||
|
||||
if (id.startsWith(sourceUid) &&
|
||||
this._events.delete(id))
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
this.emit('changed');
|
||||
}
|
||||
|
||||
_loadEvents(forceReload) {
|
||||
@@ -303,32 +336,40 @@ class DBusEventSource extends EventSourceBase {
|
||||
return;
|
||||
|
||||
if (this._curRequestBegin && this._curRequestEnd) {
|
||||
this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000,
|
||||
this._curRequestEnd.getTime() / 1000,
|
||||
forceReload,
|
||||
this._onEventsReceived.bind(this),
|
||||
Gio.DBusCallFlags.NONE);
|
||||
if (forceReload) {
|
||||
this._events.clear();
|
||||
this.emit('changed');
|
||||
}
|
||||
this._dbusProxy.SetTimeRangeRemote(this._curRequestBegin.getTime() / 1000,
|
||||
this._curRequestEnd.getTime() / 1000,
|
||||
forceReload,
|
||||
Gio.DBusCallFlags.NONE);
|
||||
}
|
||||
}
|
||||
|
||||
requestRange(begin, end) {
|
||||
if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
|
||||
this._isLoading = true;
|
||||
this._lastRequestBegin = begin;
|
||||
this._lastRequestEnd = end;
|
||||
this._curRequestBegin = begin;
|
||||
this._curRequestEnd = end;
|
||||
this._loadEvents(false);
|
||||
this._loadEvents(true);
|
||||
}
|
||||
}
|
||||
|
||||
getEvents(begin, end) {
|
||||
getEvents(begin, end, onlyCheckExistence) {
|
||||
let result = [];
|
||||
for (let n = 0; n < this._events.length; n++) {
|
||||
let event = this._events[n];
|
||||
let eventsIter = this._events.values();
|
||||
|
||||
if (_dateIntervalsOverlap(event.date, event.end, begin, end))
|
||||
for (let item = eventsIter.next(); !item.done; item = eventsIter.next()) {
|
||||
let event = item.value;
|
||||
|
||||
if (_dateIntervalsOverlap(event.date, event.end, begin, end)) {
|
||||
result.push(event);
|
||||
|
||||
if (onlyCheckExistence)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
result.sort((event1, event2) => {
|
||||
// sort events by end time on ending day
|
||||
@@ -343,7 +384,7 @@ class DBusEventSource extends EventSourceBase {
|
||||
let dayBegin = _getBeginningOfDay(day);
|
||||
let dayEnd = _getEndOfDay(day);
|
||||
|
||||
let events = this.getEvents(dayBegin, dayEnd);
|
||||
let events = this.getEvents(dayBegin, dayEnd, true);
|
||||
|
||||
if (events.length == 0)
|
||||
return false;
|
||||
@@ -873,7 +914,7 @@ class EventsSection extends MessageList.MessageListSection {
|
||||
}
|
||||
|
||||
_reloadEvents() {
|
||||
if (this._eventSource.isLoading)
|
||||
if (this._eventSource.isLoading || this._reloading)
|
||||
return;
|
||||
|
||||
this._reloading = true;
|
||||
@@ -882,10 +923,7 @@ class EventsSection extends MessageList.MessageListSection {
|
||||
let periodEnd = _getEndOfDay(this._date);
|
||||
let events = this._eventSource.getEvents(periodBegin, periodEnd);
|
||||
|
||||
let ids = events.map(e => e.id);
|
||||
this._messageById.forEach((message, id) => {
|
||||
if (ids.includes(id))
|
||||
return;
|
||||
this._messageById.delete(id);
|
||||
this.removeMessage(message);
|
||||
});
|
||||
|
@@ -320,7 +320,7 @@ function init() {
|
||||
_localTimeZone = GLib.TimeZone.new_local();
|
||||
|
||||
let dt = GLib.DateTime.new(_localTimeZone,
|
||||
this.getYear(),
|
||||
this.getFullYear(),
|
||||
this.getMonth() + 1,
|
||||
this.getDate(),
|
||||
this.getHours(),
|
||||
|
@@ -19,7 +19,7 @@ cogl_pango_pc = 'mutter-cogl-pango-' + mutter_api_version
|
||||
libmutter_pc = 'libmutter-' + mutter_api_version
|
||||
|
||||
ecal_req = '>= 3.33.1'
|
||||
eds_req = '>= 3.17.2'
|
||||
eds_req = '>= 3.33.1'
|
||||
gcr_req = '>= 3.7.5'
|
||||
gio_req = '>= 2.56.0'
|
||||
gi_req = '>= 1.49.1'
|
||||
|
@@ -74,6 +74,7 @@ js/ui/windowAttentionHandler.js
|
||||
js/ui/windowManager.js
|
||||
js/ui/windowMenu.js
|
||||
src/calendar-server/evolution-calendar.desktop.in
|
||||
src/calendar-server/reminder-watcher.c
|
||||
src/main.c
|
||||
src/shell-app.c
|
||||
src/shell-app-system.c
|
||||
|
257
po/lt.po
257
po/lt.po
@@ -10,8 +10,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: gnome-shell master\n"
|
||||
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/gnome-shell/issues\n"
|
||||
"POT-Creation-Date: 2020-02-17 11:08+0000\n"
|
||||
"PO-Revision-Date: 2020-02-17 22:38+0200\n"
|
||||
"POT-Creation-Date: 2020-02-27 21:57+0000\n"
|
||||
"PO-Revision-Date: 2020-03-01 20:41+0200\n"
|
||||
"Last-Translator: Aurimas Černius <aurisc4@gmail.com>\n"
|
||||
"Language-Team: Lietuvių <gnome-lt@lists.akl.lt>\n"
|
||||
"Language: lt\n"
|
||||
@@ -410,11 +410,11 @@ msgstr ""
|
||||
"Jei pašalinsite plėtinį, reikės grįžti prie jo atsisiuntimo, jei norėsite jį "
|
||||
"vėl įjungti"
|
||||
|
||||
#: js/extensionPrefs/main.js:144 js/ui/audioDeviceSelection.js:57
|
||||
#: js/ui/components/networkAgent.js:107 js/ui/components/polkitAgent.js:139
|
||||
#: js/ui/endSessionDialog.js:374 js/ui/extensionDownloader.js:166
|
||||
#: js/ui/shellMountOperation.js:376 js/ui/shellMountOperation.js:386
|
||||
#: js/ui/status/network.js:910
|
||||
#: js/extensionPrefs/main.js:144 js/gdm/authPrompt.js:135
|
||||
#: js/ui/audioDeviceSelection.js:57 js/ui/components/networkAgent.js:107
|
||||
#: js/ui/components/polkitAgent.js:139 js/ui/endSessionDialog.js:374
|
||||
#: js/ui/extensionDownloader.js:165 js/ui/shellMountOperation.js:376
|
||||
#: js/ui/shellMountOperation.js:386 js/ui/status/network.js:913
|
||||
msgid "Cancel"
|
||||
msgstr "Atsisakyti"
|
||||
|
||||
@@ -546,12 +546,11 @@ msgid "Log Out…"
|
||||
msgstr "Atsijungti…"
|
||||
|
||||
#. Cisco LEAP
|
||||
#: js/gdm/authPrompt.js:235 js/ui/components/networkAgent.js:202
|
||||
#: js/ui/components/networkAgent.js:214 js/ui/components/networkAgent.js:238
|
||||
#: js/ui/components/networkAgent.js:259 js/ui/components/networkAgent.js:279
|
||||
#: js/ui/components/networkAgent.js:289 js/ui/components/polkitAgent.js:277
|
||||
#: js/gdm/authPrompt.js:237 js/ui/components/networkAgent.js:202
|
||||
#: js/ui/components/networkAgent.js:218 js/ui/components/networkAgent.js:242
|
||||
#: js/ui/components/networkAgent.js:263 js/ui/components/networkAgent.js:283
|
||||
#: js/ui/components/networkAgent.js:293 js/ui/components/polkitAgent.js:277
|
||||
#: js/ui/shellMountOperation.js:326
|
||||
#| msgid "Password:"
|
||||
msgid "Password"
|
||||
msgstr "Slaptažodis"
|
||||
|
||||
@@ -573,9 +572,8 @@ msgstr "(pvz., naudotojas arba %s)"
|
||||
#. TTLS and PEAP are actually much more complicated, but this complication
|
||||
#. is not visible here since we only care about phase2 authentication
|
||||
#. (and don't even care of which one)
|
||||
#: js/gdm/loginDialog.js:917 js/ui/components/networkAgent.js:234
|
||||
#: js/ui/components/networkAgent.js:257 js/ui/components/networkAgent.js:275
|
||||
#| msgid "Username: "
|
||||
#: js/gdm/loginDialog.js:917 js/ui/components/networkAgent.js:238
|
||||
#: js/ui/components/networkAgent.js:261 js/ui/components/networkAgent.js:279
|
||||
msgid "Username"
|
||||
msgstr "Naudotojo vardas"
|
||||
|
||||
@@ -810,11 +808,11 @@ msgid "%B %-d %Y, %l∶%M %p"
|
||||
msgstr "%Y %m %-d, %l∶%M %p"
|
||||
|
||||
#. TRANSLATORS: this is the title of the wifi captive portal login window
|
||||
#: js/portalHelper/main.js:42
|
||||
#: js/portalHelper/main.js:41
|
||||
msgid "Hotspot Login"
|
||||
msgstr "Prisijungimas prie prieigos taško"
|
||||
|
||||
#: js/portalHelper/main.js:88
|
||||
#: js/portalHelper/main.js:87
|
||||
msgid ""
|
||||
"Your connection to this hotspot login is not secure. Passwords or other "
|
||||
"information you enter on this page can be viewed by people nearby."
|
||||
@@ -849,27 +847,27 @@ msgid "All"
|
||||
msgstr "Visos"
|
||||
|
||||
#. Translators: This is the heading of a list of open windows
|
||||
#: js/ui/appDisplay.js:2454 js/ui/panel.js:75
|
||||
#: js/ui/appDisplay.js:2450 js/ui/panel.js:75
|
||||
msgid "Open Windows"
|
||||
msgstr "Atverti langai"
|
||||
|
||||
#: js/ui/appDisplay.js:2474 js/ui/panel.js:82
|
||||
#: js/ui/appDisplay.js:2470 js/ui/panel.js:82
|
||||
msgid "New Window"
|
||||
msgstr "Naujas langas"
|
||||
|
||||
#: js/ui/appDisplay.js:2485
|
||||
#: js/ui/appDisplay.js:2481
|
||||
msgid "Launch using Dedicated Graphics Card"
|
||||
msgstr "Paleisti naudojant dedikuotą grafikos kortą"
|
||||
|
||||
#: js/ui/appDisplay.js:2513 js/ui/dash.js:239
|
||||
#: js/ui/appDisplay.js:2509 js/ui/dash.js:239
|
||||
msgid "Remove from Favorites"
|
||||
msgstr "Pašalinti iš mėgstamų"
|
||||
|
||||
#: js/ui/appDisplay.js:2519
|
||||
#: js/ui/appDisplay.js:2515
|
||||
msgid "Add to Favorites"
|
||||
msgstr "Pridėti prie mėgstamų"
|
||||
|
||||
#: js/ui/appDisplay.js:2529 js/ui/panel.js:93
|
||||
#: js/ui/appDisplay.js:2525 js/ui/panel.js:93
|
||||
msgid "Show Details"
|
||||
msgstr "Rodyti detalią informaciją"
|
||||
|
||||
@@ -1094,37 +1092,32 @@ msgstr ""
|
||||
"Taip pat galite prisijungti paspausdami „WPS“ mygtuką savo maršrutizatoriuje."
|
||||
|
||||
#: js/ui/components/networkAgent.js:101 js/ui/status/network.js:223
|
||||
#: js/ui/status/network.js:314 js/ui/status/network.js:913
|
||||
#: js/ui/status/network.js:314 js/ui/status/network.js:916
|
||||
msgid "Connect"
|
||||
msgstr "Prisijungti"
|
||||
|
||||
#. static WEP
|
||||
#: js/ui/components/networkAgent.js:207
|
||||
#| msgid "Key: "
|
||||
#: js/ui/components/networkAgent.js:208
|
||||
msgid "Key"
|
||||
msgstr "Raktas"
|
||||
|
||||
#: js/ui/components/networkAgent.js:242 js/ui/components/networkAgent.js:265
|
||||
#| msgid "Private key password: "
|
||||
#: js/ui/components/networkAgent.js:246 js/ui/components/networkAgent.js:269
|
||||
msgid "Private key password"
|
||||
msgstr "Privataus rakto slaptažodis"
|
||||
|
||||
#: js/ui/components/networkAgent.js:263
|
||||
#| msgid "Identity: "
|
||||
#: js/ui/components/networkAgent.js:267
|
||||
msgid "Identity"
|
||||
msgstr "Tapatybė"
|
||||
|
||||
#: js/ui/components/networkAgent.js:277
|
||||
#| msgid "Service: "
|
||||
#: js/ui/components/networkAgent.js:281
|
||||
msgid "Service"
|
||||
msgstr "Tarnyba"
|
||||
|
||||
#: js/ui/components/networkAgent.js:306 js/ui/components/networkAgent.js:334
|
||||
#: js/ui/components/networkAgent.js:681 js/ui/components/networkAgent.js:702
|
||||
#: js/ui/components/networkAgent.js:310 js/ui/components/networkAgent.js:338
|
||||
#: js/ui/components/networkAgent.js:685 js/ui/components/networkAgent.js:706
|
||||
msgid "Authentication required"
|
||||
msgstr "Reikia patvirtinti tapatybę"
|
||||
|
||||
#: js/ui/components/networkAgent.js:307 js/ui/components/networkAgent.js:682
|
||||
#: js/ui/components/networkAgent.js:311 js/ui/components/networkAgent.js:686
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Passwords or encryption keys are required to access the wireless network "
|
||||
@@ -1133,44 +1126,42 @@ msgstr ""
|
||||
"Slaptažodžiai arba šifravimo raktai yra būtini priėjimui prie belaidžio "
|
||||
"tinklo „%s“."
|
||||
|
||||
#: js/ui/components/networkAgent.js:311 js/ui/components/networkAgent.js:686
|
||||
#: js/ui/components/networkAgent.js:315 js/ui/components/networkAgent.js:690
|
||||
msgid "Wired 802.1X authentication"
|
||||
msgstr "Laidinis 802.1X tapatybės patvirtinimas"
|
||||
|
||||
#: js/ui/components/networkAgent.js:313
|
||||
#| msgid "Network name: "
|
||||
#: js/ui/components/networkAgent.js:317
|
||||
msgid "Network name"
|
||||
msgstr "Tinklo vardas"
|
||||
|
||||
#: js/ui/components/networkAgent.js:318 js/ui/components/networkAgent.js:690
|
||||
#: js/ui/components/networkAgent.js:322 js/ui/components/networkAgent.js:694
|
||||
msgid "DSL authentication"
|
||||
msgstr "DSL tapatybės patvirtinimas"
|
||||
|
||||
#: js/ui/components/networkAgent.js:325 js/ui/components/networkAgent.js:695
|
||||
#: js/ui/components/networkAgent.js:329 js/ui/components/networkAgent.js:699
|
||||
msgid "PIN code required"
|
||||
msgstr "Reikalingas PIN kodas"
|
||||
|
||||
#: js/ui/components/networkAgent.js:326 js/ui/components/networkAgent.js:696
|
||||
#: js/ui/components/networkAgent.js:330 js/ui/components/networkAgent.js:700
|
||||
msgid "PIN code is needed for the mobile broadband device"
|
||||
msgstr "Reikalingas PIN kodas mobiliajam plačiajuosčiam įrenginiui"
|
||||
|
||||
#: js/ui/components/networkAgent.js:327
|
||||
#| msgid "PIN: "
|
||||
#: js/ui/components/networkAgent.js:331
|
||||
msgid "PIN"
|
||||
msgstr "PIN"
|
||||
|
||||
#: js/ui/components/networkAgent.js:335 js/ui/components/networkAgent.js:687
|
||||
#: js/ui/components/networkAgent.js:691 js/ui/components/networkAgent.js:703
|
||||
#: js/ui/components/networkAgent.js:707
|
||||
#: js/ui/components/networkAgent.js:339 js/ui/components/networkAgent.js:691
|
||||
#: js/ui/components/networkAgent.js:695 js/ui/components/networkAgent.js:707
|
||||
#: js/ui/components/networkAgent.js:711
|
||||
#, javascript-format
|
||||
msgid "A password is required to connect to “%s”."
|
||||
msgstr "Būtinas slaptažodis norint prisijungti prie „%s“."
|
||||
|
||||
#: js/ui/components/networkAgent.js:670 js/ui/status/network.js:1688
|
||||
#: js/ui/components/networkAgent.js:674 js/ui/status/network.js:1691
|
||||
msgid "Network Manager"
|
||||
msgstr "Tinklo tvarkymas"
|
||||
|
||||
#: js/ui/components/networkAgent.js:706
|
||||
#: js/ui/components/networkAgent.js:710
|
||||
msgid "VPN password"
|
||||
msgstr "VPN slaptažodis"
|
||||
|
||||
@@ -1414,15 +1405,15 @@ msgstr "%s (nutolęs)"
|
||||
msgid "%s (console)"
|
||||
msgstr "%s (komandų eilutė)"
|
||||
|
||||
#: js/ui/extensionDownloader.js:170
|
||||
#: js/ui/extensionDownloader.js:169
|
||||
msgid "Install"
|
||||
msgstr "Įdiegti"
|
||||
|
||||
#: js/ui/extensionDownloader.js:176
|
||||
#: js/ui/extensionDownloader.js:175
|
||||
msgid "Install Extension"
|
||||
msgstr "Išdiegti plėtinį"
|
||||
|
||||
#: js/ui/extensionDownloader.js:177
|
||||
#: js/ui/extensionDownloader.js:176
|
||||
#, javascript-format
|
||||
msgid "Download and install “%s” from extensions.gnome.org?"
|
||||
msgstr "Parsiųsti ir įdiegti „%s“ iš extensions.gnome.org?"
|
||||
@@ -1510,13 +1501,13 @@ msgid "Leave On"
|
||||
msgstr "Palikti įjungtą"
|
||||
|
||||
#: js/ui/kbdA11yDialog.js:55 js/ui/status/bluetooth.js:135
|
||||
#: js/ui/status/network.js:1285
|
||||
#: js/ui/status/network.js:1288
|
||||
msgid "Turn On"
|
||||
msgstr "Įjungti"
|
||||
|
||||
#: js/ui/kbdA11yDialog.js:63 js/ui/status/bluetooth.js:135
|
||||
#: js/ui/status/network.js:131 js/ui/status/network.js:315
|
||||
#: js/ui/status/network.js:1285 js/ui/status/network.js:1397
|
||||
#: js/ui/status/network.js:1288 js/ui/status/network.js:1400
|
||||
#: js/ui/status/nightLight.js:41 js/ui/status/rfkill.js:81
|
||||
#: js/ui/status/rfkill.js:108
|
||||
msgid "Turn Off"
|
||||
@@ -1530,59 +1521,59 @@ msgstr "Palikti išjungtą"
|
||||
msgid "Region & Language Settings"
|
||||
msgstr "Regiono ir kalbos nustatymai"
|
||||
|
||||
#: js/ui/lookingGlass.js:659
|
||||
#: js/ui/lookingGlass.js:665
|
||||
msgid "No extensions installed"
|
||||
msgstr "Nėra įdiegtų plėtinių"
|
||||
|
||||
#. Translators: argument is an extension UUID.
|
||||
#: js/ui/lookingGlass.js:714
|
||||
#: js/ui/lookingGlass.js:720
|
||||
#, javascript-format
|
||||
msgid "%s has not emitted any errors."
|
||||
msgstr "%s nepranešė apie jokias klaidas."
|
||||
|
||||
#: js/ui/lookingGlass.js:720
|
||||
#: js/ui/lookingGlass.js:726
|
||||
msgid "Hide Errors"
|
||||
msgstr "Slėpti klaidas"
|
||||
|
||||
#: js/ui/lookingGlass.js:724 js/ui/lookingGlass.js:789
|
||||
#: js/ui/lookingGlass.js:730 js/ui/lookingGlass.js:795
|
||||
msgid "Show Errors"
|
||||
msgstr "Rodyti klaidas"
|
||||
|
||||
#: js/ui/lookingGlass.js:733
|
||||
#: js/ui/lookingGlass.js:739
|
||||
msgid "Enabled"
|
||||
msgstr "Įjungta"
|
||||
|
||||
#. translators:
|
||||
#. * The device has been disabled
|
||||
#: js/ui/lookingGlass.js:736 subprojects/gvc/gvc-mixer-control.c:1892
|
||||
#: js/ui/lookingGlass.js:742 subprojects/gvc/gvc-mixer-control.c:1892
|
||||
msgid "Disabled"
|
||||
msgstr "Išjungta"
|
||||
|
||||
#: js/ui/lookingGlass.js:738
|
||||
#: js/ui/lookingGlass.js:744
|
||||
msgid "Error"
|
||||
msgstr "Klaida"
|
||||
|
||||
#: js/ui/lookingGlass.js:740
|
||||
#: js/ui/lookingGlass.js:746
|
||||
msgid "Out of date"
|
||||
msgstr "Pasenęs"
|
||||
|
||||
#: js/ui/lookingGlass.js:742
|
||||
#: js/ui/lookingGlass.js:748
|
||||
msgid "Downloading"
|
||||
msgstr "Atsiunčiama"
|
||||
|
||||
#: js/ui/lookingGlass.js:771
|
||||
#: js/ui/lookingGlass.js:777
|
||||
msgid "View Source"
|
||||
msgstr "Žiūrėti šaltinį"
|
||||
|
||||
#: js/ui/lookingGlass.js:780
|
||||
#: js/ui/lookingGlass.js:786
|
||||
msgid "Web Page"
|
||||
msgstr "Tinklalapis"
|
||||
|
||||
#: js/ui/main.js:267
|
||||
#: js/ui/main.js:269
|
||||
msgid "Logged in as a privileged user"
|
||||
msgstr "Prisijungta privilegijuotu naudotoju"
|
||||
|
||||
#: js/ui/main.js:268
|
||||
#: js/ui/main.js:270
|
||||
msgid ""
|
||||
"Running a session as a privileged user should be avoided for security "
|
||||
"reasons. If possible, you should log in as a normal user."
|
||||
@@ -1590,15 +1581,15 @@ msgstr ""
|
||||
"Saugumo sumetimais turėtų būti vengiama vykdyti seansus privilegijuotais "
|
||||
"naudotojais. Jei įmanoma, turėtumėt visada prisjungti normaliu naudotoju."
|
||||
|
||||
#: js/ui/main.js:274
|
||||
#: js/ui/main.js:276
|
||||
msgid "Screen Lock disabled"
|
||||
msgstr "Ekrano užraktas išjungtas"
|
||||
|
||||
#: js/ui/main.js:275
|
||||
#: js/ui/main.js:277
|
||||
msgid "Screen Locking requires the GNOME display manager."
|
||||
msgstr "Ekrano užrakinimas reikalaujas GNOME vaizduoklio valdyklės."
|
||||
|
||||
#: js/ui/messageTray.js:1552
|
||||
#: js/ui/messageTray.js:1554
|
||||
msgid "System Information"
|
||||
msgstr "Sistemos informacija"
|
||||
|
||||
@@ -1625,7 +1616,6 @@ msgstr "Apžvalga"
|
||||
#. active; it should not exceed ~30
|
||||
#. characters.
|
||||
#: js/ui/overview.js:107
|
||||
#| msgid "Type to search…"
|
||||
msgid "Type to search"
|
||||
msgstr "Rašykite, ko ieškote"
|
||||
|
||||
@@ -1653,23 +1643,23 @@ msgstr "Priskirti klavišų kombinaciją"
|
||||
msgid "Done"
|
||||
msgstr "Atlikta"
|
||||
|
||||
#: js/ui/padOsd.js:747
|
||||
#: js/ui/padOsd.js:745
|
||||
msgid "Edit…"
|
||||
msgstr "Keisti…"
|
||||
|
||||
#: js/ui/padOsd.js:789 js/ui/padOsd.js:912
|
||||
#: js/ui/padOsd.js:787 js/ui/padOsd.js:910
|
||||
msgid "None"
|
||||
msgstr "Nėra"
|
||||
|
||||
#: js/ui/padOsd.js:865
|
||||
#: js/ui/padOsd.js:863
|
||||
msgid "Press a button to configure"
|
||||
msgstr "Spauskite mygtuką konfigūravimui"
|
||||
|
||||
#: js/ui/padOsd.js:866
|
||||
#: js/ui/padOsd.js:864
|
||||
msgid "Press Esc to exit"
|
||||
msgstr "Spauskit Esc išėjimui"
|
||||
|
||||
#: js/ui/padOsd.js:869
|
||||
#: js/ui/padOsd.js:867
|
||||
msgid "Press any key to exit"
|
||||
msgstr "Išėjimui spauskite bet kurį klavišą"
|
||||
|
||||
@@ -1683,12 +1673,12 @@ msgstr "Užverti"
|
||||
msgid "Activities"
|
||||
msgstr "Apžvalga"
|
||||
|
||||
#: js/ui/panel.js:707
|
||||
#: js/ui/panel.js:713
|
||||
msgctxt "System menu in the top bar"
|
||||
msgid "System"
|
||||
msgstr "Sistema"
|
||||
|
||||
#: js/ui/panel.js:820
|
||||
#: js/ui/panel.js:826
|
||||
msgid "Top Bar"
|
||||
msgstr "Viršutinė juosta"
|
||||
|
||||
@@ -1719,11 +1709,11 @@ msgstr "GNOME aplinkai reikia užrakinti ekraną"
|
||||
#.
|
||||
#. XXX: another option is to kick the user into the gdm login
|
||||
#. screen, where we're not affected by grabs
|
||||
#: js/ui/screenShield.js:244 js/ui/screenShield.js:602
|
||||
#: js/ui/screenShield.js:244 js/ui/screenShield.js:598
|
||||
msgid "Unable to lock"
|
||||
msgstr "Nepavyksta užrakinti"
|
||||
|
||||
#: js/ui/screenShield.js:245 js/ui/screenShield.js:603
|
||||
#: js/ui/screenShield.js:245 js/ui/screenShield.js:599
|
||||
msgid "Lock was blocked by an application"
|
||||
msgstr "Programa užblokavo užrakinimą"
|
||||
|
||||
@@ -1785,7 +1775,6 @@ msgstr ""
|
||||
"įrankį."
|
||||
|
||||
#: js/ui/shellMountOperation.js:306
|
||||
#| msgid "Enter PIM Number…"
|
||||
msgid "PIM Number"
|
||||
msgstr "PIM numeris"
|
||||
|
||||
@@ -1867,7 +1856,7 @@ msgstr "Didelis tekstas"
|
||||
msgid "Bluetooth"
|
||||
msgstr "Bluetooth"
|
||||
|
||||
#: js/ui/status/bluetooth.js:49 js/ui/status/network.js:590
|
||||
#: js/ui/status/bluetooth.js:49 js/ui/status/network.js:591
|
||||
msgid "Bluetooth Settings"
|
||||
msgstr "Bluetooth nustatymai"
|
||||
|
||||
@@ -1912,11 +1901,11 @@ msgstr "Antrinis paspaudimas"
|
||||
msgid "Dwell Click"
|
||||
msgstr "Uždelstas paspaudimas"
|
||||
|
||||
#: js/ui/status/keyboard.js:825
|
||||
#: js/ui/status/keyboard.js:826
|
||||
msgid "Keyboard"
|
||||
msgstr "Klaviatūra"
|
||||
|
||||
#: js/ui/status/keyboard.js:847
|
||||
#: js/ui/status/keyboard.js:848
|
||||
msgid "Show Keyboard Layout"
|
||||
msgstr "Rodyti klaviatūros išdėstymą"
|
||||
|
||||
@@ -1963,7 +1952,7 @@ msgid "<unknown>"
|
||||
msgstr "<nežinoma>"
|
||||
|
||||
#. Translators: %s is a network identifier
|
||||
#: js/ui/status/network.js:420 js/ui/status/network.js:1314
|
||||
#: js/ui/status/network.js:420 js/ui/status/network.js:1317
|
||||
#, javascript-format
|
||||
msgid "%s Off"
|
||||
msgstr "%s išjungtas"
|
||||
@@ -1989,7 +1978,7 @@ msgid "%s Disconnecting"
|
||||
msgstr "Atsijungiama nuo %s"
|
||||
|
||||
#. Translators: %s is a network identifier
|
||||
#: js/ui/status/network.js:438 js/ui/status/network.js:1306
|
||||
#: js/ui/status/network.js:438 js/ui/status/network.js:1309
|
||||
#, javascript-format
|
||||
msgid "%s Connecting"
|
||||
msgstr "Jungiamasi prie %s"
|
||||
@@ -2029,7 +2018,7 @@ msgid "Mobile Broadband Settings"
|
||||
msgstr "Mobiliojo plačiajuosčio tinklo nustatymai"
|
||||
|
||||
#. Translators: %s is a network identifier
|
||||
#: js/ui/status/network.js:558 js/ui/status/network.js:1311
|
||||
#: js/ui/status/network.js:558 js/ui/status/network.js:1314
|
||||
#, javascript-format
|
||||
msgid "%s Hardware Disabled"
|
||||
msgstr "%s aparatinė įranga išjungta"
|
||||
@@ -2041,100 +2030,100 @@ msgstr "%s aparatinė įranga išjungta"
|
||||
msgid "%s Disabled"
|
||||
msgstr "%s išjungtas"
|
||||
|
||||
#: js/ui/status/network.js:602
|
||||
#: js/ui/status/network.js:603
|
||||
msgid "Connect to Internet"
|
||||
msgstr "Prisijungti prie interneto"
|
||||
|
||||
#: js/ui/status/network.js:805
|
||||
#: js/ui/status/network.js:808
|
||||
msgid "Airplane Mode is On"
|
||||
msgstr "Skrydžio veiksena įjungta"
|
||||
|
||||
#: js/ui/status/network.js:806
|
||||
#: js/ui/status/network.js:809
|
||||
msgid "Wi-Fi is disabled when airplane mode is on."
|
||||
msgstr "Belaidis ryšys yra išjungta skrydžio veiksenoje."
|
||||
|
||||
#: js/ui/status/network.js:807
|
||||
#: js/ui/status/network.js:810
|
||||
msgid "Turn Off Airplane Mode"
|
||||
msgstr "Išjungti skrydžio veikseną"
|
||||
|
||||
#: js/ui/status/network.js:816
|
||||
#: js/ui/status/network.js:819
|
||||
msgid "Wi-Fi is Off"
|
||||
msgstr "Belaidžio ryšys išjungtas"
|
||||
|
||||
#: js/ui/status/network.js:817
|
||||
#: js/ui/status/network.js:820
|
||||
msgid "Wi-Fi needs to be turned on in order to connect to a network."
|
||||
msgstr "Norint prisijungti prie tinklo reikia įjungti belaidį ryšį."
|
||||
|
||||
#: js/ui/status/network.js:818
|
||||
#: js/ui/status/network.js:821
|
||||
msgid "Turn On Wi-Fi"
|
||||
msgstr "Įjungti belaidį ryšį"
|
||||
|
||||
#: js/ui/status/network.js:843
|
||||
#: js/ui/status/network.js:846
|
||||
msgid "Wi-Fi Networks"
|
||||
msgstr "Wi-Fi tinklai"
|
||||
|
||||
#: js/ui/status/network.js:845
|
||||
#: js/ui/status/network.js:848
|
||||
msgid "Select a network"
|
||||
msgstr "Pasirinkite tinklą"
|
||||
|
||||
#: js/ui/status/network.js:877
|
||||
#: js/ui/status/network.js:880
|
||||
msgid "No Networks"
|
||||
msgstr "Nėra tinklų"
|
||||
|
||||
#: js/ui/status/network.js:898 js/ui/status/rfkill.js:106
|
||||
#: js/ui/status/network.js:901 js/ui/status/rfkill.js:106
|
||||
msgid "Use hardware switch to turn off"
|
||||
msgstr "Išjungimui naudoti aparatinį jungiklį"
|
||||
|
||||
#: js/ui/status/network.js:1175
|
||||
#: js/ui/status/network.js:1178
|
||||
msgid "Select Network"
|
||||
msgstr "Pasirinkite tinklą"
|
||||
|
||||
#: js/ui/status/network.js:1181
|
||||
#: js/ui/status/network.js:1184
|
||||
msgid "Wi-Fi Settings"
|
||||
msgstr "Belaidžio ryšio nustatymai"
|
||||
|
||||
#. Translators: %s is a network identifier
|
||||
#: js/ui/status/network.js:1302
|
||||
#: js/ui/status/network.js:1305
|
||||
#, javascript-format
|
||||
msgid "%s Hotspot Active"
|
||||
msgstr "Prieigos taškas %s aktyvus"
|
||||
|
||||
#. Translators: %s is a network identifier
|
||||
#: js/ui/status/network.js:1317
|
||||
#: js/ui/status/network.js:1320
|
||||
#, javascript-format
|
||||
msgid "%s Not Connected"
|
||||
msgstr "Neprisijungta prie %s"
|
||||
|
||||
#: js/ui/status/network.js:1414
|
||||
#: js/ui/status/network.js:1417
|
||||
msgid "connecting…"
|
||||
msgstr "jungiamasi…"
|
||||
|
||||
#. Translators: this is for network connections that require some kind of key or password
|
||||
#: js/ui/status/network.js:1417
|
||||
#: js/ui/status/network.js:1420
|
||||
msgid "authentication required"
|
||||
msgstr "reikia patvirtinti tapatybę"
|
||||
|
||||
#: js/ui/status/network.js:1419
|
||||
#: js/ui/status/network.js:1422
|
||||
msgid "connection failed"
|
||||
msgstr "nepavyko prisijungti"
|
||||
|
||||
#: js/ui/status/network.js:1470
|
||||
#: js/ui/status/network.js:1473
|
||||
msgid "VPN Settings"
|
||||
msgstr "VPN nustatymai"
|
||||
|
||||
#: js/ui/status/network.js:1487
|
||||
#: js/ui/status/network.js:1490
|
||||
msgid "VPN"
|
||||
msgstr "VPN"
|
||||
|
||||
#: js/ui/status/network.js:1497
|
||||
#: js/ui/status/network.js:1500
|
||||
msgid "VPN Off"
|
||||
msgstr "VPN išjungtas"
|
||||
|
||||
#: js/ui/status/network.js:1558 js/ui/status/rfkill.js:84
|
||||
#: js/ui/status/network.js:1561 js/ui/status/rfkill.js:84
|
||||
msgid "Network Settings"
|
||||
msgstr "Tinklo nustatymai"
|
||||
|
||||
#: js/ui/status/network.js:1587
|
||||
#: js/ui/status/network.js:1590
|
||||
#, javascript-format
|
||||
msgid "%s Wired Connection"
|
||||
msgid_plural "%s Wired Connections"
|
||||
@@ -2142,7 +2131,7 @@ msgstr[0] "%s laidinis ryšys"
|
||||
msgstr[1] "%s laidiniai ryšiai"
|
||||
msgstr[2] "%s laidinių ryšių"
|
||||
|
||||
#: js/ui/status/network.js:1591
|
||||
#: js/ui/status/network.js:1594
|
||||
#, javascript-format
|
||||
msgid "%s Wi-Fi Connection"
|
||||
msgid_plural "%s Wi-Fi Connections"
|
||||
@@ -2150,7 +2139,7 @@ msgstr[0] "%s belaidis ryšys"
|
||||
msgstr[1] "%s belaidžiai ryšiai"
|
||||
msgstr[2] "%s belaidžių ryšių"
|
||||
|
||||
#: js/ui/status/network.js:1595
|
||||
#: js/ui/status/network.js:1598
|
||||
#, javascript-format
|
||||
msgid "%s Modem Connection"
|
||||
msgid_plural "%s Modem Connections"
|
||||
@@ -2158,11 +2147,11 @@ msgstr[0] "%s modemo ryšys"
|
||||
msgstr[1] "%s modemo ryšiai"
|
||||
msgstr[2] "%s modemo ryšių"
|
||||
|
||||
#: js/ui/status/network.js:1729
|
||||
#: js/ui/status/network.js:1732
|
||||
msgid "Connection failed"
|
||||
msgstr "Nepavyko prisijungti"
|
||||
|
||||
#: js/ui/status/network.js:1730
|
||||
#: js/ui/status/network.js:1733
|
||||
msgid "Activation of network connection failed"
|
||||
msgstr "Tinklo ryšio nepavyko aktyvuoti"
|
||||
|
||||
@@ -2260,11 +2249,11 @@ msgstr "Išjungti…"
|
||||
msgid "Thunderbolt"
|
||||
msgstr "Thunderbolt"
|
||||
|
||||
#: js/ui/status/thunderbolt.js:324
|
||||
#: js/ui/status/thunderbolt.js:325
|
||||
msgid "Unknown Thunderbolt device"
|
||||
msgstr "Nežinoma Thunderbolt įrenginys"
|
||||
|
||||
#: js/ui/status/thunderbolt.js:325
|
||||
#: js/ui/status/thunderbolt.js:326
|
||||
msgid ""
|
||||
"New device has been detected while you were away. Please disconnect and "
|
||||
"reconnect the device to start using it."
|
||||
@@ -2272,20 +2261,20 @@ msgstr ""
|
||||
"Jums nesant aptiktas naujas įrenginys. Atjunkite ir vėl prijunkite įrenginį, "
|
||||
"jei norite jį naudoti."
|
||||
|
||||
#: js/ui/status/thunderbolt.js:328
|
||||
#: js/ui/status/thunderbolt.js:329
|
||||
msgid "Unauthorized Thunderbolt device"
|
||||
msgstr "Neleidžiamas Thunderbolt įrenginys"
|
||||
|
||||
#: js/ui/status/thunderbolt.js:329
|
||||
#: js/ui/status/thunderbolt.js:330
|
||||
msgid ""
|
||||
"New device has been detected and needs to be authorized by an administrator."
|
||||
msgstr "Buvo aptiktas naujas įrenginys, kurį turi leisti administratorius."
|
||||
|
||||
#: js/ui/status/thunderbolt.js:335
|
||||
#: js/ui/status/thunderbolt.js:336
|
||||
msgid "Thunderbolt authorization error"
|
||||
msgstr "Thunderbolt autorizacijos klaida"
|
||||
|
||||
#: js/ui/status/thunderbolt.js:336
|
||||
#: js/ui/status/thunderbolt.js:337
|
||||
#, javascript-format
|
||||
msgid "Could not authorize the Thunderbolt device: %s"
|
||||
msgstr "Nepavyko autorizuoti Thunderbolt įrenginio: %s"
|
||||
@@ -2326,10 +2315,30 @@ msgstr "Tik išorinis"
|
||||
msgid "Built-in Only"
|
||||
msgstr "Tik vidinis"
|
||||
|
||||
#: js/ui/unlockDialog.js:552
|
||||
#. Translators: This is a time format for a date in
|
||||
#. long format
|
||||
#: js/ui/unlockDialog.js:370
|
||||
#| msgctxt "calendar heading"
|
||||
#| msgid "%A, %B %-d"
|
||||
msgid "%A %B %-d"
|
||||
msgstr "%A %B %-d"
|
||||
|
||||
#: js/ui/unlockDialog.js:376
|
||||
msgid "Swipe up to unlock"
|
||||
msgstr "Atrakinkite perbraukdami"
|
||||
|
||||
#: js/ui/unlockDialog.js:377
|
||||
msgid "Click or press a key to unlock"
|
||||
msgstr "Atrakinkite mygtuko ar klavišo paspaudimu"
|
||||
|
||||
#: js/ui/unlockDialog.js:549
|
||||
msgid "Unlock Window"
|
||||
msgstr "Atrakinimo langas"
|
||||
|
||||
#: js/ui/unlockDialog.js:558
|
||||
msgid "Log in as another user"
|
||||
msgstr "Prisijungti kitu naudotoju"
|
||||
|
||||
#: js/ui/viewSelector.js:181
|
||||
msgid "Applications"
|
||||
msgstr "Programos"
|
||||
@@ -2369,7 +2378,7 @@ msgstr[2] "Pakeitimai bus grąžinti po %d sekundžių"
|
||||
|
||||
#. Translators: This represents the size of a window. The first number is
|
||||
#. * the width of the window and the second is the height.
|
||||
#: js/ui/windowManager.js:542
|
||||
#: js/ui/windowManager.js:544
|
||||
#, javascript-format
|
||||
msgid "%d × %d"
|
||||
msgstr "%d × %d"
|
||||
@@ -2630,7 +2639,6 @@ msgid "Show extensions with preferences"
|
||||
msgstr "Rodyti plėtinius su nuostatomis"
|
||||
|
||||
#: subprojects/extensions-tool/src/command-list.c:140
|
||||
#| msgid "Show extensions with preferences"
|
||||
msgid "Show extensions with updates"
|
||||
msgstr "Rodyti plėtinius su atnaujinimais"
|
||||
|
||||
@@ -2851,9 +2859,6 @@ msgstr "Sistemos garsai"
|
||||
#~ msgstr[1] "%d nauji pranešimai"
|
||||
#~ msgstr[2] "%d naujų pranešimų"
|
||||
|
||||
#~ msgid "Log in as another user"
|
||||
#~ msgstr "Prisijungti kitu naudotoju"
|
||||
|
||||
#~ msgid "Logout…"
|
||||
#~ msgstr "Atsijungti…"
|
||||
|
||||
|
@@ -43,20 +43,7 @@ struct _ClientData
|
||||
{
|
||||
ECalClient *client;
|
||||
gulong backend_died_id;
|
||||
};
|
||||
|
||||
struct _CalendarSourceData
|
||||
{
|
||||
ECalClientSourceType source_type;
|
||||
CalendarSources *sources;
|
||||
guint changed_signal;
|
||||
|
||||
/* ESource -> EClient */
|
||||
GHashTable *clients;
|
||||
|
||||
guint timeout_id;
|
||||
|
||||
guint loaded : 1;
|
||||
gboolean is_for_events; /* Because this can hold other clients too (for EReminderWatcher) */
|
||||
};
|
||||
|
||||
typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
|
||||
@@ -69,175 +56,150 @@ struct _CalendarSources
|
||||
|
||||
struct _CalendarSourcesPrivate
|
||||
{
|
||||
ESourceRegistry *registry;
|
||||
gulong source_added_id;
|
||||
gulong source_changed_id;
|
||||
gulong source_removed_id;
|
||||
ESourceRegistryWatcher *registry_watcher;
|
||||
gulong filter_id;
|
||||
gulong appeared_id;
|
||||
gulong disappeared_id;
|
||||
|
||||
CalendarSourceData appointment_sources;
|
||||
CalendarSourceData task_sources;
|
||||
GMutex clients_lock;
|
||||
GHashTable *clients; /* ESource -> ClientData */
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (CalendarSources, calendar_sources, G_TYPE_OBJECT)
|
||||
|
||||
static void calendar_sources_finalize (GObject *object);
|
||||
|
||||
static void backend_died_cb (EClient *client, CalendarSourceData *source_data);
|
||||
static void calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
|
||||
ESource *source,
|
||||
CalendarSources *sources);
|
||||
static void calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
|
||||
ESource *source,
|
||||
CalendarSources *sources);
|
||||
|
||||
enum
|
||||
{
|
||||
APPOINTMENT_SOURCES_CHANGED,
|
||||
TASK_SOURCES_CHANGED,
|
||||
CLIENT_APPEARED,
|
||||
CLIENT_DISAPPEARED,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
static guint signals [LAST_SIGNAL] = { 0, };
|
||||
|
||||
static GObjectClass *parent_class = NULL;
|
||||
static CalendarSources *calendar_sources_singleton = NULL;
|
||||
static void
|
||||
calendar_sources_client_connected_cb (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
CalendarSources *sources = CALENDAR_SOURCES (source_object);
|
||||
ESource *source = user_data;
|
||||
EClient *client;
|
||||
GError *error = NULL;
|
||||
|
||||
/* The calendar_sources_connect_client_sync() already stored the 'client'
|
||||
* into the priv->clients */
|
||||
client = calendar_sources_connect_client_finish (sources, result, &error);
|
||||
if (error)
|
||||
{
|
||||
g_warning ("Could not load source '%s': %s",
|
||||
e_source_get_uid (source),
|
||||
error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_signal_emit (sources, signals[CLIENT_APPEARED], 0, client, NULL);
|
||||
}
|
||||
|
||||
g_clear_object (&client);
|
||||
g_clear_object (&source);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
registry_watcher_filter_cb (ESourceRegistryWatcher *watcher,
|
||||
ESource *source,
|
||||
CalendarSources *sources)
|
||||
{
|
||||
return e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR) &&
|
||||
e_source_selectable_get_selected (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR));
|
||||
}
|
||||
|
||||
static void
|
||||
registry_watcher_source_appeared_cb (ESourceRegistryWatcher *watcher,
|
||||
ESource *source,
|
||||
CalendarSources *sources)
|
||||
{
|
||||
ECalClientSourceType source_type;
|
||||
|
||||
if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
|
||||
source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
|
||||
else if (e_source_has_extension (source, E_SOURCE_EXTENSION_MEMO_LIST))
|
||||
source_type = E_CAL_CLIENT_SOURCE_TYPE_MEMOS;
|
||||
else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
|
||||
source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
|
||||
else
|
||||
g_return_if_reached ();
|
||||
|
||||
calendar_sources_connect_client (sources, TRUE, source, source_type, 30, NULL, calendar_sources_client_connected_cb, g_object_ref (source));
|
||||
}
|
||||
|
||||
static void
|
||||
registry_watcher_source_disappeared_cb (ESourceRegistryWatcher *watcher,
|
||||
ESource *source,
|
||||
CalendarSources *sources)
|
||||
{
|
||||
gboolean emit;
|
||||
|
||||
g_mutex_lock (&sources->priv->clients_lock);
|
||||
|
||||
emit = g_hash_table_remove (sources->priv->clients, source);
|
||||
|
||||
g_mutex_unlock (&sources->priv->clients_lock);
|
||||
|
||||
if (emit)
|
||||
g_signal_emit (sources, signals[CLIENT_DISAPPEARED], 0, e_source_get_uid (source), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
client_data_free (ClientData *data)
|
||||
{
|
||||
g_clear_signal_handler (&data->backend_died_id, data->client);
|
||||
g_signal_handler_disconnect (data->client, data->backend_died_id);
|
||||
g_object_unref (data->client);
|
||||
g_slice_free (ClientData, data);
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_class_init (CalendarSourcesClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
|
||||
parent_class = g_type_class_peek_parent (klass);
|
||||
|
||||
gobject_class->finalize = calendar_sources_finalize;
|
||||
|
||||
signals [APPOINTMENT_SOURCES_CHANGED] =
|
||||
g_signal_new ("appointment-sources-changed",
|
||||
G_TYPE_FROM_CLASS (gobject_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
|
||||
signals [TASK_SOURCES_CHANGED] =
|
||||
g_signal_new ("task-sources-changed",
|
||||
G_TYPE_FROM_CLASS (gobject_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_init (CalendarSources *sources)
|
||||
calendar_sources_constructed (GObject *object)
|
||||
{
|
||||
CalendarSources *sources = CALENDAR_SOURCES (object);
|
||||
ESourceRegistry *registry = NULL;
|
||||
GError *error = NULL;
|
||||
GDBusConnection *session_bus;
|
||||
GVariant *result;
|
||||
|
||||
sources->priv = calendar_sources_get_instance_private (sources);
|
||||
|
||||
/* WORKAROUND: the hardcoded timeout for e_source_registry_new_sync()
|
||||
(and other library calls that eventually call g_dbus_proxy_new[_sync]())
|
||||
is 25 seconds. This has been shown to be too small for
|
||||
evolution-source-registry in certain cases (slow disk, concurrent IO,
|
||||
many configured sources), so we first ensure that the service
|
||||
starts with a manual call and a higher timeout.
|
||||
|
||||
HACK: every time the DBus API is bumped in e-d-s we need
|
||||
to update this!
|
||||
*/
|
||||
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
|
||||
if (session_bus == NULL)
|
||||
{
|
||||
g_error ("Failed to connect to the session bus: %s", error->message);
|
||||
}
|
||||
|
||||
result = g_dbus_connection_call_sync (session_bus, "org.freedesktop.DBus",
|
||||
"/", "org.freedesktop.DBus",
|
||||
"StartServiceByName",
|
||||
g_variant_new ("(su)",
|
||||
"org.gnome.evolution.dataserver.Sources5",
|
||||
0),
|
||||
NULL,
|
||||
G_DBUS_CALL_FLAGS_NONE,
|
||||
60 * 1000,
|
||||
NULL, &error);
|
||||
if (result != NULL)
|
||||
{
|
||||
g_variant_unref (result);
|
||||
sources->priv->registry = e_source_registry_new_sync (NULL, &error);
|
||||
}
|
||||
G_OBJECT_CLASS (calendar_sources_parent_class)->constructed (object);
|
||||
|
||||
registry = e_source_registry_new_sync (NULL, &error);
|
||||
if (error != NULL)
|
||||
{
|
||||
/* Any error is fatal, but we don't want to crash gnome-shell-calendar-server
|
||||
because of e-d-s problems. So just exit here.
|
||||
*/
|
||||
g_warning ("Failed to start evolution-source-registry: %s", error->message);
|
||||
exit(EXIT_FAILURE);
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
g_object_unref (session_bus);
|
||||
g_return_if_fail (registry != NULL);
|
||||
|
||||
sources->priv->source_added_id = g_signal_connect (sources->priv->registry,
|
||||
"source-added",
|
||||
G_CALLBACK (calendar_sources_registry_source_changed_cb),
|
||||
sources);
|
||||
sources->priv->source_changed_id = g_signal_connect (sources->priv->registry,
|
||||
"source-changed",
|
||||
G_CALLBACK (calendar_sources_registry_source_changed_cb),
|
||||
sources);
|
||||
sources->priv->source_removed_id = g_signal_connect (sources->priv->registry,
|
||||
"source-removed",
|
||||
G_CALLBACK (calendar_sources_registry_source_removed_cb),
|
||||
sources);
|
||||
sources->priv->registry_watcher = e_source_registry_watcher_new (registry, NULL);
|
||||
|
||||
sources->priv->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
|
||||
sources->priv->appointment_sources.sources = sources;
|
||||
sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
|
||||
sources->priv->appointment_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
|
||||
(GEqualFunc) e_source_equal,
|
||||
(GDestroyNotify) g_object_unref,
|
||||
(GDestroyNotify) client_data_free);
|
||||
sources->priv->appointment_sources.timeout_id = 0;
|
||||
g_clear_object (®istry);
|
||||
|
||||
sources->priv->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
|
||||
sources->priv->task_sources.sources = sources;
|
||||
sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
|
||||
sources->priv->task_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
|
||||
(GEqualFunc) e_source_equal,
|
||||
(GDestroyNotify) g_object_unref,
|
||||
(GDestroyNotify) client_data_free);
|
||||
sources->priv->task_sources.timeout_id = 0;
|
||||
}
|
||||
sources->priv->clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
|
||||
(GEqualFunc) e_source_equal,
|
||||
(GDestroyNotify) g_object_unref,
|
||||
(GDestroyNotify) client_data_free);
|
||||
sources->priv->filter_id = g_signal_connect (sources->priv->registry_watcher,
|
||||
"filter",
|
||||
G_CALLBACK (registry_watcher_filter_cb),
|
||||
sources);
|
||||
sources->priv->appeared_id = g_signal_connect (sources->priv->registry_watcher,
|
||||
"appeared",
|
||||
G_CALLBACK (registry_watcher_source_appeared_cb),
|
||||
sources);
|
||||
sources->priv->disappeared_id = g_signal_connect (sources->priv->registry_watcher,
|
||||
"disappeared",
|
||||
G_CALLBACK (registry_watcher_source_disappeared_cb),
|
||||
sources);
|
||||
|
||||
static void
|
||||
calendar_sources_finalize_source_data (CalendarSources *sources,
|
||||
CalendarSourceData *source_data)
|
||||
{
|
||||
if (source_data->loaded)
|
||||
{
|
||||
g_hash_table_destroy (source_data->clients);
|
||||
source_data->clients = NULL;
|
||||
|
||||
g_clear_handle_id (&source_data->timeout_id, g_source_remove);
|
||||
|
||||
source_data->loaded = FALSE;
|
||||
}
|
||||
e_source_registry_watcher_reclaim (sources->priv->registry_watcher);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -245,28 +207,73 @@ calendar_sources_finalize (GObject *object)
|
||||
{
|
||||
CalendarSources *sources = CALENDAR_SOURCES (object);
|
||||
|
||||
if (sources->priv->registry)
|
||||
if (sources->priv->clients)
|
||||
{
|
||||
g_clear_signal_handler (&sources->priv->source_added_id,
|
||||
sources->priv->registry);
|
||||
g_clear_signal_handler (&sources->priv->source_changed_id,
|
||||
sources->priv->registry);
|
||||
g_clear_signal_handler (&sources->priv->source_removed_id,
|
||||
sources->priv->registry);
|
||||
g_object_unref (sources->priv->registry);
|
||||
g_hash_table_destroy (sources->priv->clients);
|
||||
sources->priv->clients = NULL;
|
||||
}
|
||||
sources->priv->registry = NULL;
|
||||
|
||||
calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
|
||||
calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
|
||||
if (sources->priv->registry_watcher)
|
||||
{
|
||||
g_signal_handler_disconnect (sources->priv->registry_watcher,
|
||||
sources->priv->filter_id);
|
||||
g_signal_handler_disconnect (sources->priv->registry_watcher,
|
||||
sources->priv->appeared_id);
|
||||
g_signal_handler_disconnect (sources->priv->registry_watcher,
|
||||
sources->priv->disappeared_id);
|
||||
g_clear_object (&sources->priv->registry_watcher);
|
||||
}
|
||||
|
||||
if (G_OBJECT_CLASS (parent_class)->finalize)
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
g_mutex_clear (&sources->priv->clients_lock);
|
||||
|
||||
G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_class_init (CalendarSourcesClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
|
||||
gobject_class->constructed = calendar_sources_constructed;
|
||||
gobject_class->finalize = calendar_sources_finalize;
|
||||
|
||||
signals [CLIENT_APPEARED] =
|
||||
g_signal_new ("client-appeared",
|
||||
G_TYPE_FROM_CLASS (gobject_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
E_TYPE_CAL_CLIENT);
|
||||
|
||||
signals [CLIENT_DISAPPEARED] =
|
||||
g_signal_new ("client-disappeared",
|
||||
G_TYPE_FROM_CLASS (gobject_class),
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE,
|
||||
1,
|
||||
G_TYPE_STRING); /* ESource::uid of the disappeared client */
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_init (CalendarSources *sources)
|
||||
{
|
||||
sources->priv = calendar_sources_get_instance_private (sources);
|
||||
|
||||
g_mutex_init (&sources->priv->clients_lock);
|
||||
}
|
||||
|
||||
CalendarSources *
|
||||
calendar_sources_get (void)
|
||||
{
|
||||
static CalendarSources *calendar_sources_singleton = NULL;
|
||||
gpointer singleton_location = &calendar_sources_singleton;
|
||||
|
||||
if (calendar_sources_singleton)
|
||||
@@ -274,85 +281,70 @@ calendar_sources_get (void)
|
||||
|
||||
calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
|
||||
singleton_location);
|
||||
singleton_location);
|
||||
|
||||
return calendar_sources_singleton;
|
||||
}
|
||||
|
||||
/* The clients are just created here but not loaded */
|
||||
static void
|
||||
create_client_for_source (ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
CalendarSourceData *source_data)
|
||||
ESourceRegistry *
|
||||
calendar_sources_get_registry (CalendarSources *sources)
|
||||
{
|
||||
ClientData *data;
|
||||
EClient *client;
|
||||
GError *error = NULL;
|
||||
|
||||
client = g_hash_table_lookup (source_data->clients, source);
|
||||
g_return_if_fail (client == NULL);
|
||||
|
||||
client = e_cal_client_connect_sync (source, source_type, -1, NULL, &error);
|
||||
if (!client)
|
||||
{
|
||||
g_warning ("Could not load source '%s': %s",
|
||||
e_source_get_uid (source),
|
||||
error->message);
|
||||
g_clear_error(&error);
|
||||
return;
|
||||
}
|
||||
|
||||
data = g_slice_new0 (ClientData);
|
||||
data->client = E_CAL_CLIENT (client); /* takes ownership */
|
||||
data->backend_died_id = g_signal_connect (client,
|
||||
"backend-died",
|
||||
G_CALLBACK (backend_died_cb),
|
||||
source_data);
|
||||
|
||||
g_hash_table_insert (source_data->clients, g_object_ref (source), data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
debug_dump_ecal_list (GHashTable *clients)
|
||||
{
|
||||
#ifdef CALENDAR_ENABLE_DEBUG
|
||||
GList *list, *link;
|
||||
|
||||
dprintf ("Loaded clients:\n");
|
||||
list = g_hash_table_get_keys (clients);
|
||||
for (link = list; link != NULL; link = g_list_next (link))
|
||||
{
|
||||
ESource *source = E_SOURCE (link->data);
|
||||
|
||||
dprintf (" %s %s\n",
|
||||
e_source_get_uid (source),
|
||||
e_source_get_display_name (source));
|
||||
}
|
||||
g_list_free (list);
|
||||
#endif
|
||||
return e_source_registry_watcher_get_registry (sources->priv->registry_watcher);
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_load_esource_list (ESourceRegistry *registry,
|
||||
CalendarSourceData *source_data);
|
||||
|
||||
static gboolean
|
||||
backend_restart (gpointer data)
|
||||
gather_event_clients_cb (gpointer key,
|
||||
gpointer value,
|
||||
gpointer user_data)
|
||||
{
|
||||
CalendarSourceData *source_data = data;
|
||||
ESourceRegistry *registry;
|
||||
GSList **plist = user_data;
|
||||
ClientData *cd = value;
|
||||
|
||||
registry = source_data->sources->priv->registry;
|
||||
calendar_sources_load_esource_list (registry, source_data);
|
||||
g_signal_emit (source_data->sources, source_data->changed_signal, 0);
|
||||
if (cd && cd->is_for_events)
|
||||
*plist = g_slist_prepend (*plist, g_object_ref (cd->client));
|
||||
}
|
||||
|
||||
source_data->timeout_id = 0;
|
||||
|
||||
return FALSE;
|
||||
GSList *
|
||||
calendar_sources_ref_clients (CalendarSources *sources)
|
||||
{
|
||||
GSList *list = NULL;
|
||||
|
||||
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
|
||||
|
||||
g_mutex_lock (&sources->priv->clients_lock);
|
||||
g_hash_table_foreach (sources->priv->clients, gather_event_clients_cb, &list);
|
||||
g_mutex_unlock (&sources->priv->clients_lock);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
gboolean
|
||||
calendar_sources_has_clients (CalendarSources *sources)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
gpointer value;
|
||||
gboolean has = FALSE;
|
||||
|
||||
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
|
||||
|
||||
g_mutex_lock (&sources->priv->clients_lock);
|
||||
|
||||
g_hash_table_iter_init (&iter, sources->priv->clients);
|
||||
while (!has && g_hash_table_iter_next (&iter, NULL, &value))
|
||||
{
|
||||
ClientData *cd = value;
|
||||
|
||||
has = cd && cd->is_for_events;
|
||||
}
|
||||
|
||||
g_mutex_unlock (&sources->priv->clients_lock);
|
||||
|
||||
return has;
|
||||
}
|
||||
|
||||
static void
|
||||
backend_died_cb (EClient *client, CalendarSourceData *source_data)
|
||||
backend_died_cb (EClient *client,
|
||||
CalendarSources *sources)
|
||||
{
|
||||
ESource *source;
|
||||
const char *display_name;
|
||||
@@ -360,196 +352,179 @@ backend_died_cb (EClient *client, CalendarSourceData *source_data)
|
||||
source = e_client_get_source (client);
|
||||
display_name = e_source_get_display_name (source);
|
||||
g_warning ("The calendar backend for '%s' has crashed.", display_name);
|
||||
g_hash_table_remove (source_data->clients, source);
|
||||
|
||||
g_clear_handle_id (&source_data->timeout_id, g_source_remove);
|
||||
|
||||
source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
|
||||
source_data);
|
||||
g_source_set_name_by_id (source_data->timeout_id, "[gnome-shell] backend_restart");
|
||||
g_mutex_lock (&sources->priv->clients_lock);
|
||||
g_hash_table_remove (sources->priv->clients, source);
|
||||
g_mutex_unlock (&sources->priv->clients_lock);
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_load_esource_list (ESourceRegistry *registry,
|
||||
CalendarSourceData *source_data)
|
||||
EClient *
|
||||
calendar_sources_connect_client_sync (CalendarSources *sources,
|
||||
gboolean is_for_events,
|
||||
ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
guint32 wait_for_connected_seconds,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
GList *list, *link;
|
||||
const gchar *extension_name;
|
||||
EClient *client = NULL;
|
||||
ClientData *client_data;
|
||||
|
||||
switch (source_data->source_type)
|
||||
g_mutex_lock (&sources->priv->clients_lock);
|
||||
client_data = g_hash_table_lookup (sources->priv->clients, source);
|
||||
if (client_data) {
|
||||
if (is_for_events)
|
||||
client_data->is_for_events = TRUE;
|
||||
client = E_CLIENT (g_object_ref (client_data->client));
|
||||
}
|
||||
g_mutex_unlock (&sources->priv->clients_lock);
|
||||
|
||||
if (client)
|
||||
return client;
|
||||
|
||||
client = e_cal_client_connect_sync (source, source_type, wait_for_connected_seconds, cancellable, error);
|
||||
if (!client)
|
||||
return NULL;
|
||||
|
||||
g_mutex_lock (&sources->priv->clients_lock);
|
||||
client_data = g_hash_table_lookup (sources->priv->clients, source);
|
||||
if (client_data)
|
||||
{
|
||||
case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
|
||||
extension_name = E_SOURCE_EXTENSION_CALENDAR;
|
||||
break;
|
||||
case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
|
||||
extension_name = E_SOURCE_EXTENSION_TASK_LIST;
|
||||
break;
|
||||
default:
|
||||
g_return_if_reached ();
|
||||
if (is_for_events)
|
||||
client_data->is_for_events = TRUE;
|
||||
g_clear_object (&client);
|
||||
client = E_CLIENT (g_object_ref (client_data->client));
|
||||
}
|
||||
|
||||
list = e_source_registry_list_sources (registry, extension_name);
|
||||
|
||||
for (link = list; link != NULL; link = g_list_next (link))
|
||||
else
|
||||
{
|
||||
ESource *source = E_SOURCE (link->data);
|
||||
ESourceSelectable *extension;
|
||||
gboolean show_source;
|
||||
client_data = g_slice_new0 (ClientData);
|
||||
client_data->client = E_CAL_CLIENT (g_object_ref (client));
|
||||
client_data->is_for_events = is_for_events;
|
||||
client_data->backend_died_id = g_signal_connect (client,
|
||||
"backend-died",
|
||||
G_CALLBACK (backend_died_cb),
|
||||
sources);
|
||||
|
||||
extension = e_source_get_extension (source, extension_name);
|
||||
show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
|
||||
|
||||
if (show_source)
|
||||
create_client_for_source (source, source_data->source_type, source_data);
|
||||
g_hash_table_insert (sources->priv->clients, g_object_ref (source), client_data);
|
||||
}
|
||||
g_mutex_unlock (&sources->priv->clients_lock);
|
||||
|
||||
debug_dump_ecal_list (source_data->clients);
|
||||
|
||||
g_list_free_full (list, g_object_unref);
|
||||
return client;
|
||||
}
|
||||
|
||||
typedef struct _AsyncContext {
|
||||
gboolean is_for_events;
|
||||
ESource *source;
|
||||
ECalClientSourceType source_type;
|
||||
guint32 wait_for_connected_seconds;
|
||||
} AsyncContext;
|
||||
|
||||
static void
|
||||
calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
|
||||
ESource *source,
|
||||
CalendarSources *sources)
|
||||
async_context_free (gpointer ptr)
|
||||
{
|
||||
if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
|
||||
AsyncContext *ctx = ptr;
|
||||
|
||||
if (ctx)
|
||||
{
|
||||
CalendarSourceData *source_data;
|
||||
ESourceSelectable *extension;
|
||||
gboolean have_client;
|
||||
gboolean show_source;
|
||||
|
||||
source_data = &sources->priv->appointment_sources;
|
||||
extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
|
||||
have_client = (g_hash_table_lookup (source_data->clients, source) != NULL);
|
||||
show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
|
||||
|
||||
if (!show_source && have_client)
|
||||
{
|
||||
g_hash_table_remove (source_data->clients, source);
|
||||
g_signal_emit (sources, source_data->changed_signal, 0);
|
||||
}
|
||||
if (show_source && !have_client)
|
||||
{
|
||||
create_client_for_source (source, source_data->source_type, source_data);
|
||||
g_signal_emit (sources, source_data->changed_signal, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
|
||||
{
|
||||
CalendarSourceData *source_data;
|
||||
ESourceSelectable *extension;
|
||||
gboolean have_client;
|
||||
gboolean show_source;
|
||||
|
||||
source_data = &sources->priv->task_sources;
|
||||
extension = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
|
||||
have_client = (g_hash_table_lookup (source_data->clients, source) != NULL);
|
||||
show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
|
||||
|
||||
if (!show_source && have_client)
|
||||
{
|
||||
g_hash_table_remove (source_data->clients, source);
|
||||
g_signal_emit (sources, source_data->changed_signal, 0);
|
||||
}
|
||||
if (show_source && !have_client)
|
||||
{
|
||||
create_client_for_source (source, source_data->source_type, source_data);
|
||||
g_signal_emit (sources, source_data->changed_signal, 0);
|
||||
}
|
||||
g_clear_object (&ctx->source);
|
||||
g_slice_free (AsyncContext, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
|
||||
ESource *source,
|
||||
CalendarSources *sources)
|
||||
calendar_sources_connect_client_thread (GTask *task,
|
||||
gpointer source_object,
|
||||
gpointer task_data,
|
||||
GCancellable *cancellable)
|
||||
{
|
||||
if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
|
||||
CalendarSources *sources = source_object;
|
||||
AsyncContext *ctx = task_data;
|
||||
EClient *client;
|
||||
GError *local_error = NULL;
|
||||
|
||||
client = calendar_sources_connect_client_sync (sources, ctx->is_for_events, ctx->source, ctx->source_type,
|
||||
ctx->wait_for_connected_seconds, cancellable, &local_error);
|
||||
if (!client)
|
||||
{
|
||||
CalendarSourceData *source_data;
|
||||
|
||||
source_data = &sources->priv->appointment_sources;
|
||||
g_hash_table_remove (source_data->clients, source);
|
||||
g_signal_emit (sources, source_data->changed_signal, 0);
|
||||
}
|
||||
|
||||
if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
|
||||
{
|
||||
CalendarSourceData *source_data;
|
||||
|
||||
source_data = &sources->priv->task_sources;
|
||||
g_hash_table_remove (source_data->clients, source);
|
||||
g_signal_emit (sources, source_data->changed_signal, 0);
|
||||
if (local_error)
|
||||
g_task_return_error (task, local_error);
|
||||
else
|
||||
g_task_return_pointer (task, NULL, NULL);
|
||||
} else {
|
||||
g_task_return_pointer (task, client, g_object_unref);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_appointment_sources (CalendarSources *sources)
|
||||
void
|
||||
calendar_sources_connect_client (CalendarSources *sources,
|
||||
gboolean is_for_events,
|
||||
ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
guint32 wait_for_connected_seconds,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
if (!sources->priv->appointment_sources.loaded)
|
||||
AsyncContext *ctx;
|
||||
GTask *task;
|
||||
|
||||
ctx = g_slice_new0 (AsyncContext);
|
||||
ctx->is_for_events = is_for_events;
|
||||
ctx->source = g_object_ref (source);
|
||||
ctx->source_type = source_type;
|
||||
ctx->wait_for_connected_seconds = wait_for_connected_seconds;
|
||||
|
||||
task = g_task_new (sources, cancellable, callback, user_data);
|
||||
g_task_set_source_tag (task, calendar_sources_connect_client);
|
||||
g_task_set_task_data (task, ctx, async_context_free);
|
||||
|
||||
g_task_run_in_thread (task, calendar_sources_connect_client_thread);
|
||||
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
EClient *
|
||||
calendar_sources_connect_client_finish (CalendarSources *sources,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, sources), NULL);
|
||||
g_return_val_if_fail (g_async_result_is_tagged (result, calendar_sources_connect_client), NULL);
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
print_debug (const gchar *format,
|
||||
...)
|
||||
{
|
||||
g_autofree char *s = NULL;
|
||||
g_autofree char *timestamp = NULL;
|
||||
va_list ap;
|
||||
g_autoptr (GDateTime) now = NULL;
|
||||
static volatile gsize once_init_value = 0;
|
||||
static gboolean show_debug = FALSE;
|
||||
static guint pid = 0;
|
||||
|
||||
if (g_once_init_enter (&once_init_value))
|
||||
{
|
||||
calendar_sources_load_esource_list (sources->priv->registry,
|
||||
&sources->priv->appointment_sources);
|
||||
sources->priv->appointment_sources.loaded = TRUE;
|
||||
show_debug = (g_getenv ("CALENDAR_SERVER_DEBUG") != NULL);
|
||||
pid = getpid ();
|
||||
g_once_init_leave (&once_init_value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
GList *
|
||||
calendar_sources_get_appointment_clients (CalendarSources *sources)
|
||||
{
|
||||
GList *list, *link;
|
||||
|
||||
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
|
||||
|
||||
ensure_appointment_sources (sources);
|
||||
|
||||
list = g_hash_table_get_values (sources->priv->appointment_sources.clients);
|
||||
|
||||
for (link = list; link != NULL; link = g_list_next (link))
|
||||
link->data = ((ClientData *) link->data)->client;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static void
|
||||
ensure_task_sources (CalendarSources *sources)
|
||||
{
|
||||
if (!sources->priv->task_sources.loaded)
|
||||
{
|
||||
calendar_sources_load_esource_list (sources->priv->registry,
|
||||
&sources->priv->task_sources);
|
||||
sources->priv->task_sources.loaded = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
GList *
|
||||
calendar_sources_get_task_clients (CalendarSources *sources)
|
||||
{
|
||||
GList *list, *link;
|
||||
|
||||
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
|
||||
|
||||
ensure_task_sources (sources);
|
||||
|
||||
list = g_hash_table_get_values (sources->priv->task_sources.clients);
|
||||
|
||||
for (link = list; link != NULL; link = g_list_next (link))
|
||||
link->data = ((ClientData *) link->data)->client;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
gboolean
|
||||
calendar_sources_has_sources (CalendarSources *sources)
|
||||
{
|
||||
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), FALSE);
|
||||
|
||||
ensure_appointment_sources (sources);
|
||||
ensure_task_sources (sources);
|
||||
|
||||
return g_hash_table_size (sources->priv->appointment_sources.clients) > 0 ||
|
||||
g_hash_table_size (sources->priv->task_sources.clients) > 0;
|
||||
|
||||
if (!show_debug)
|
||||
goto out;
|
||||
|
||||
now = g_date_time_new_now_local ();
|
||||
timestamp = g_date_time_format (now, "%H:%M:%S");
|
||||
|
||||
va_start (ap, format);
|
||||
s = g_strdup_vprintf (format, ap);
|
||||
va_end (ap);
|
||||
|
||||
g_print ("gnome-shell-calendar-server[%d]: %s.%03d: %s\n",
|
||||
pid, timestamp, g_date_time_get_microsecond (now), s);
|
||||
out:
|
||||
;
|
||||
}
|
||||
|
@@ -26,17 +26,46 @@
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
#define EDS_DISABLE_DEPRECATED
|
||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||
#include <libedataserver/libedataserver.h>
|
||||
#include <libecal/libecal.h>
|
||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (CalendarSources, calendar_sources,
|
||||
CALENDAR, SOURCES, GObject)
|
||||
|
||||
CalendarSources *calendar_sources_get (void);
|
||||
GList *calendar_sources_get_appointment_clients (CalendarSources *sources);
|
||||
GList *calendar_sources_get_task_clients (CalendarSources *sources);
|
||||
CalendarSources *calendar_sources_get (void);
|
||||
ESourceRegistry *calendar_sources_get_registry (CalendarSources *sources);
|
||||
GSList *calendar_sources_ref_clients (CalendarSources *sources);
|
||||
gboolean calendar_sources_has_clients (CalendarSources *sources);
|
||||
|
||||
gboolean calendar_sources_has_sources (CalendarSources *sources);
|
||||
EClient *calendar_sources_connect_client_sync(CalendarSources *sources,
|
||||
gboolean is_for_events,
|
||||
ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
guint32 wait_for_connected_seconds,
|
||||
GCancellable *cancellable,
|
||||
GError **error);
|
||||
void calendar_sources_connect_client (CalendarSources *sources,
|
||||
gboolean is_for_events,
|
||||
ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
guint32 wait_for_connected_seconds,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data);
|
||||
EClient *calendar_sources_connect_client_finish
|
||||
(CalendarSources *sources,
|
||||
GAsyncResult *result,
|
||||
GError **error);
|
||||
|
||||
/* Set the environment variable CALENDAR_SERVER_DEBUG to show debug */
|
||||
void print_debug (const gchar *str,
|
||||
...) G_GNUC_PRINTF (1, 2);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,9 @@ calendar_sources = [
|
||||
'gnome-shell-calendar-server.c',
|
||||
'calendar-debug.h',
|
||||
'calendar-sources.c',
|
||||
'calendar-sources.h'
|
||||
'calendar-sources.h',
|
||||
'reminder-watcher.c',
|
||||
'reminder-watcher.h'
|
||||
]
|
||||
|
||||
calendar_server = executable('gnome-shell-calendar-server', calendar_sources,
|
||||
|
767
src/calendar-server/reminder-watcher.c
Normal file
767
src/calendar-server/reminder-watcher.c
Normal file
@@ -0,0 +1,767 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Red Hat (www.redhat.com)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <glib/gi18n-lib.h>
|
||||
|
||||
#define HANDLE_LIBICAL_MEMORY
|
||||
#define EDS_DISABLE_DEPRECATED
|
||||
#include <libecal/libecal.h>
|
||||
|
||||
#include "calendar-sources.h"
|
||||
#include "reminder-watcher.h"
|
||||
|
||||
struct _ReminderWatcherPrivate {
|
||||
GApplication *application; /* not referenced */
|
||||
CalendarSources *sources;
|
||||
GSettings *settings;
|
||||
|
||||
GMutex dismiss_lock;
|
||||
GSList *dismiss; /* EReminderData * */
|
||||
GThread *dismiss_thread; /* not referenced, only to know whether it's scheduled */
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (ReminderWatcher, reminder_watcher, E_TYPE_REMINDER_WATCHER)
|
||||
|
||||
static const gchar *
|
||||
reminder_watcher_get_rd_summary (const EReminderData *rd)
|
||||
{
|
||||
if (!rd)
|
||||
return NULL;
|
||||
|
||||
return i_cal_component_get_summary (e_cal_component_get_icalcomponent (e_reminder_data_get_component (rd)));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
reminder_watcher_notify_audio (ReminderWatcher *rw,
|
||||
const EReminderData *rd,
|
||||
ECalComponentAlarm *alarm)
|
||||
{
|
||||
#if 0
|
||||
ICalAttach *attach = NULL;
|
||||
GSList *attachments;
|
||||
gboolean did_play = FALSE;
|
||||
|
||||
g_return_val_if_fail (rw != NULL, FALSE);
|
||||
g_return_val_if_fail (rd != NULL, FALSE);
|
||||
g_return_val_if_fail (alarm != NULL, FALSE);
|
||||
|
||||
attachments = e_cal_component_alarm_get_attachments (alarm);
|
||||
if (attachments && !attachments->next)
|
||||
attach = attachments->data;
|
||||
|
||||
if (attach && i_cal_attach_get_is_url (attach))
|
||||
{
|
||||
const gchar *url;
|
||||
|
||||
url = i_cal_attach_get_url (attach);
|
||||
if (url && *url)
|
||||
{
|
||||
gchar *filename;
|
||||
GError *error = NULL;
|
||||
|
||||
filename = g_filename_from_uri (url, NULL, &error);
|
||||
|
||||
if (!filename)
|
||||
ean_debug_print ("Audio notify: Failed to convert URI '%s' to filename: %s\n", url, error ? error->message : "Unknown error");
|
||||
else if (g_file_test (filename, G_FILE_TEST_EXISTS))
|
||||
{
|
||||
#ifdef HAVE_CANBERRA
|
||||
gint err = ca_context_play (ca_gtk_context_get (), 0,
|
||||
CA_PROP_MEDIA_FILENAME, filename,
|
||||
NULL);
|
||||
|
||||
did_play = !err;
|
||||
|
||||
if (err)
|
||||
ean_debug_print ("Audio notify: Cannot play file '%s': %s\n", filename, ca_strerror (err));
|
||||
#else
|
||||
ean_debug_print ("Audio notify: Cannot play file '%s': Not compiled with libcanberra\n", filename);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
ean_debug_print ("Audio notify: File '%s' does not exist\n", filename);
|
||||
|
||||
g_clear_error (&error);
|
||||
g_free (filename);
|
||||
}
|
||||
else
|
||||
ean_debug_print ("Audio notify: Alarm has stored empty URL, fallback to default sound\n");
|
||||
}
|
||||
else if (!attach)
|
||||
ean_debug_print ("Audio notify: Alarm has no attachment, fallback to default sound\n");
|
||||
else
|
||||
ean_debug_print ("Audio notify: Alarm attachment is not a URL to sound file, fallback to default sound\n");
|
||||
|
||||
#ifdef HAVE_CANBERRA
|
||||
if (!did_play)
|
||||
{
|
||||
gint err = ca_context_play (ca_gtk_context_get (), 0,
|
||||
CA_PROP_EVENT_ID, "alarm-clock-elapsed",
|
||||
NULL);
|
||||
|
||||
did_play = !err;
|
||||
|
||||
if (err)
|
||||
ean_debug_print ("Audio notify: Cannot play event sound: %s\n", ca_strerror (err));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!did_play)
|
||||
{
|
||||
GdkDisplay *display;
|
||||
|
||||
display = rw->priv->window ? gtk_widget_get_display (rw->priv->window) : NULL;
|
||||
|
||||
if (!display)
|
||||
display = gdk_display_get_default ();
|
||||
|
||||
if (display)
|
||||
gdk_display_beep (display);
|
||||
else
|
||||
ean_debug_print ("Audio notify: Cannot beep, no display found\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
print_debug ("ReminderWatcher::Notify Audio for '%s'", reminder_watcher_get_rd_summary (rd));
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
reminder_watcher_build_notif_id (const EReminderData *rd)
|
||||
{
|
||||
GString *string;
|
||||
ECalComponentId *id;
|
||||
ECalComponentAlarmInstance *instance;
|
||||
|
||||
g_return_val_if_fail (rd != NULL, NULL);
|
||||
|
||||
string = g_string_sized_new (32);
|
||||
|
||||
if (e_reminder_data_get_source_uid (rd))
|
||||
{
|
||||
g_string_append (string, e_reminder_data_get_source_uid (rd));
|
||||
g_string_append_c (string, '\n');
|
||||
}
|
||||
|
||||
id = e_cal_component_get_id (e_reminder_data_get_component (rd));
|
||||
if (id)
|
||||
{
|
||||
if (e_cal_component_id_get_uid (id))
|
||||
{
|
||||
g_string_append (string, e_cal_component_id_get_uid (id));
|
||||
g_string_append_c (string, '\n');
|
||||
}
|
||||
|
||||
if (e_cal_component_id_get_rid (id))
|
||||
{
|
||||
g_string_append (string, e_cal_component_id_get_rid (id));
|
||||
g_string_append_c (string, '\n');
|
||||
}
|
||||
|
||||
e_cal_component_id_free (id);
|
||||
}
|
||||
|
||||
instance = e_reminder_data_get_instance (rd);
|
||||
|
||||
g_string_append_printf (string, "%" G_GINT64_FORMAT, (gint64) (instance ? e_cal_component_alarm_instance_get_time (instance) : -1));
|
||||
|
||||
return g_string_free (string, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
reminder_watcher_notify_display (ReminderWatcher *rw,
|
||||
const EReminderData *rd,
|
||||
ECalComponentAlarm *alarm)
|
||||
{
|
||||
GNotification *notification;
|
||||
#if 0
|
||||
GtkIconInfo *icon_info;
|
||||
#endif
|
||||
gchar *description, *notif_id;
|
||||
|
||||
g_return_val_if_fail (rw != NULL, FALSE);
|
||||
g_return_val_if_fail (rd != NULL, FALSE);
|
||||
g_return_val_if_fail (alarm != NULL, FALSE);
|
||||
|
||||
notif_id = reminder_watcher_build_notif_id (rd);
|
||||
description = e_reminder_watcher_describe_data (E_REMINDER_WATCHER (rw), rd, E_REMINDER_WATCHER_DESCRIBE_FLAG_NONE);
|
||||
|
||||
notification = g_notification_new (_("Reminders"));
|
||||
g_notification_set_body (notification, description);
|
||||
|
||||
#if 0
|
||||
icon_info = gtk_icon_theme_lookup_icon (gtk_icon_theme_get_default (), "appointment-soon", GTK_ICON_SIZE_DIALOG, 0);
|
||||
if (icon_info)
|
||||
{
|
||||
const gchar *filename;
|
||||
|
||||
filename = gtk_icon_info_get_filename (icon_info);
|
||||
if (filename && *filename)
|
||||
{
|
||||
GFile *file;
|
||||
GIcon *icon;
|
||||
|
||||
file = g_file_new_for_path (filename);
|
||||
icon = g_file_icon_new (file);
|
||||
|
||||
if (icon)
|
||||
{
|
||||
g_notification_set_icon (notification, icon);
|
||||
g_object_unref (icon);
|
||||
}
|
||||
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
gtk_icon_info_free (icon_info);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_notification_add_button_with_target (notification, _("Dismiss"), "app.dismiss-reminder", "s", notif_id);
|
||||
g_notification_set_default_action_and_target (notification, "app.dismiss-reminder", "s", notif_id);
|
||||
|
||||
g_application_send_notification (rw->priv->application, notif_id, notification);
|
||||
|
||||
g_object_unref (notification);
|
||||
g_free (description);
|
||||
g_free (notif_id);
|
||||
|
||||
print_debug ("ReminderWatcher::Notify Display for '%s'", reminder_watcher_get_rd_summary (rd));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
reminder_watcher_notify_email (ReminderWatcher *rw,
|
||||
const EReminderData *rd,
|
||||
ECalComponentAlarm *alarm)
|
||||
{
|
||||
print_debug ("ReminderWatcher::Notify Email for '%s'", reminder_watcher_get_rd_summary (rd));
|
||||
|
||||
/* Nothing to do here */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
reminder_watcher_is_blessed_program (GSettings *settings,
|
||||
const gchar *url)
|
||||
{
|
||||
gchar **list;
|
||||
gint ii;
|
||||
gboolean found = FALSE;
|
||||
|
||||
g_return_val_if_fail (G_IS_SETTINGS (settings), FALSE);
|
||||
g_return_val_if_fail (url != NULL, FALSE);
|
||||
|
||||
list = g_settings_get_strv (settings, "notify-programs");
|
||||
|
||||
for (ii = 0; list && list[ii] && !found; ii++)
|
||||
{
|
||||
found = g_strcmp0 (list[ii], url) == 0;
|
||||
}
|
||||
|
||||
g_strfreev (list);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void
|
||||
reminder_watcher_save_blessed_program (GSettings *settings,
|
||||
const gchar *url)
|
||||
{
|
||||
gchar **list;
|
||||
gint ii;
|
||||
GPtrArray *array;
|
||||
|
||||
g_return_if_fail (G_IS_SETTINGS (settings));
|
||||
g_return_if_fail (url != NULL);
|
||||
|
||||
array = g_ptr_array_new ();
|
||||
|
||||
list = g_settings_get_strv (settings, "notify-programs");
|
||||
|
||||
for (ii = 0; list && list[ii]; ii++)
|
||||
{
|
||||
if (g_strcmp0 (url, list[ii]) != 0)
|
||||
g_ptr_array_add (array, list[ii]);
|
||||
}
|
||||
|
||||
g_ptr_array_add (array, (gpointer) url);
|
||||
g_ptr_array_add (array, NULL);
|
||||
|
||||
g_settings_set_strv (settings, "notify-programs", (const gchar * const *) array->pdata);
|
||||
|
||||
g_ptr_array_free (array, TRUE);
|
||||
g_strfreev (list);
|
||||
}
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
reminder_watcher_can_procedure (ReminderWatcher *rw,
|
||||
const gchar *cmd,
|
||||
const gchar *url)
|
||||
{
|
||||
#if 0
|
||||
GtkWidget *container;
|
||||
GtkWidget *dialog;
|
||||
GtkWidget *label;
|
||||
GtkWidget *checkbox;
|
||||
gchar *str;
|
||||
gint response;
|
||||
#endif
|
||||
|
||||
if (reminder_watcher_is_blessed_program (rw->priv->settings, url))
|
||||
return TRUE;
|
||||
|
||||
#if 0
|
||||
dialog = gtk_dialog_new_with_buttons (
|
||||
_("Warning"), GTK_WINDOW (rw->priv->window), 0,
|
||||
_("_No"), GTK_RESPONSE_CANCEL,
|
||||
_("_Yes"), GTK_RESPONSE_OK,
|
||||
NULL);
|
||||
|
||||
str = g_strdup_printf (
|
||||
_("A calendar reminder is about to trigger. "
|
||||
"This reminder is configured to run the following program:\n\n"
|
||||
" %s\n\n"
|
||||
"Are you sure you want to run this program?"),
|
||||
cmd);
|
||||
label = gtk_label_new (str);
|
||||
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
|
||||
gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
|
||||
gtk_widget_show (label);
|
||||
|
||||
container = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
|
||||
gtk_box_pack_start (GTK_BOX (container), label, TRUE, TRUE, 4);
|
||||
g_free (str);
|
||||
|
||||
checkbox = gtk_check_button_new_with_label (_("Do not ask me about this program again"));
|
||||
gtk_widget_show (checkbox);
|
||||
gtk_box_pack_start (GTK_BOX (container), checkbox, TRUE, TRUE, 4);
|
||||
|
||||
response = gtk_dialog_run (GTK_DIALOG (dialog));
|
||||
|
||||
if (response == GTK_RESPONSE_OK &&
|
||||
gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (checkbox)))
|
||||
{
|
||||
reminder_watcher_save_blessed_program (rw->priv->settings, url);
|
||||
}
|
||||
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
return response == GTK_RESPONSE_OK;
|
||||
#endif
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
reminder_watcher_notify_procedure (ReminderWatcher *rw,
|
||||
const EReminderData *rd,
|
||||
ECalComponentAlarm *alarm)
|
||||
{
|
||||
ECalComponentText *description;
|
||||
ICalAttach *attach = NULL;
|
||||
GSList *attachments;
|
||||
const gchar *url;
|
||||
gchar *cmd;
|
||||
gboolean result = FALSE;
|
||||
|
||||
g_return_val_if_fail (rw != NULL, FALSE);
|
||||
g_return_val_if_fail (rd != NULL, FALSE);
|
||||
g_return_val_if_fail (alarm != NULL, FALSE);
|
||||
|
||||
print_debug ("ReminderWatcher::Notify Procedure for '%s'", reminder_watcher_get_rd_summary (rd));
|
||||
|
||||
attachments = e_cal_component_alarm_get_attachments (alarm);
|
||||
|
||||
if (attachments && !attachments->next)
|
||||
attach = attachments->data;
|
||||
|
||||
description = e_cal_component_alarm_get_description (alarm);
|
||||
|
||||
/* If the alarm has no attachment, simply display a notification dialog. */
|
||||
if (!attach)
|
||||
goto fallback;
|
||||
|
||||
if (!i_cal_attach_get_is_url (attach))
|
||||
goto fallback;
|
||||
|
||||
url = i_cal_attach_get_url (attach);
|
||||
g_return_val_if_fail (url != NULL, FALSE);
|
||||
|
||||
/* Ask for confirmation before executing the stuff */
|
||||
if (description && e_cal_component_text_get_value (description))
|
||||
cmd = g_strconcat (url, " ", e_cal_component_text_get_value (description), NULL);
|
||||
else
|
||||
cmd = (gchar *) url;
|
||||
|
||||
if (reminder_watcher_can_procedure (rw, cmd, url))
|
||||
result = g_spawn_command_line_async (cmd, NULL);
|
||||
|
||||
if (cmd != (gchar *) url)
|
||||
g_free (cmd);
|
||||
|
||||
/* Fall back to display notification if we got an error */
|
||||
if (!result)
|
||||
goto fallback;
|
||||
|
||||
return FALSE;
|
||||
|
||||
fallback:
|
||||
|
||||
return reminder_watcher_notify_display (rw, rd, alarm);
|
||||
}
|
||||
|
||||
/* Returns %TRUE to keep it, %FALSE to dismiss it */
|
||||
static gboolean
|
||||
reminders_process_one (ReminderWatcher *rw,
|
||||
const EReminderData *rd,
|
||||
gboolean snoozed)
|
||||
{
|
||||
ECalComponentAlarm *alarm;
|
||||
ECalComponentAlarmInstance *instance;
|
||||
ECalComponentAlarmAction action;
|
||||
gboolean keep_in_reminders = FALSE;
|
||||
|
||||
g_return_val_if_fail (rw != NULL, FALSE);
|
||||
g_return_val_if_fail (rd != NULL, FALSE);
|
||||
|
||||
if (e_cal_component_get_vtype (e_reminder_data_get_component (rd)) == E_CAL_COMPONENT_TODO)
|
||||
{
|
||||
ICalPropertyStatus status;
|
||||
|
||||
status = e_cal_component_get_status (e_reminder_data_get_component (rd));
|
||||
|
||||
if (status == I_CAL_STATUS_COMPLETED &&
|
||||
!g_settings_get_boolean (rw->priv->settings, "notify-completed-tasks"))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
instance = e_reminder_data_get_instance (rd);
|
||||
|
||||
alarm = instance ? e_cal_component_get_alarm (e_reminder_data_get_component (rd), e_cal_component_alarm_instance_get_uid (instance)) : NULL;
|
||||
if (!alarm)
|
||||
return FALSE;
|
||||
|
||||
if (!snoozed && !g_settings_get_boolean (rw->priv->settings, "notify-past-events"))
|
||||
{
|
||||
ECalComponentAlarmTrigger *trigger;
|
||||
time_t offset = 0, event_relative, orig_trigger_day, today;
|
||||
|
||||
trigger = e_cal_component_alarm_get_trigger (alarm);
|
||||
|
||||
switch (trigger ? e_cal_component_alarm_trigger_get_kind (trigger) : E_CAL_COMPONENT_ALARM_TRIGGER_NONE)
|
||||
{
|
||||
case E_CAL_COMPONENT_ALARM_TRIGGER_NONE:
|
||||
case E_CAL_COMPONENT_ALARM_TRIGGER_ABSOLUTE:
|
||||
break;
|
||||
|
||||
case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START:
|
||||
case E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_END:
|
||||
offset = i_cal_duration_as_int (e_cal_component_alarm_trigger_get_duration (trigger));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
today = time (NULL);
|
||||
event_relative = e_cal_component_alarm_instance_get_occur_start (instance) - offset;
|
||||
|
||||
#define CLAMP_TO_DAY(x) ((x) - ((x) % (60 * 60 * 24)))
|
||||
|
||||
event_relative = CLAMP_TO_DAY (event_relative);
|
||||
orig_trigger_day = CLAMP_TO_DAY (e_cal_component_alarm_instance_get_time (instance));
|
||||
today = CLAMP_TO_DAY (today);
|
||||
|
||||
#undef CLAMP_TO_DAY
|
||||
|
||||
if (event_relative < today && orig_trigger_day < today)
|
||||
{
|
||||
e_cal_component_alarm_free (alarm);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
action = e_cal_component_alarm_get_action (alarm);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case E_CAL_COMPONENT_ALARM_AUDIO:
|
||||
keep_in_reminders = reminder_watcher_notify_audio (rw, rd, alarm);
|
||||
break;
|
||||
|
||||
case E_CAL_COMPONENT_ALARM_DISPLAY:
|
||||
keep_in_reminders = reminder_watcher_notify_display (rw, rd, alarm);
|
||||
break;
|
||||
|
||||
case E_CAL_COMPONENT_ALARM_EMAIL:
|
||||
keep_in_reminders = reminder_watcher_notify_email (rw, rd, alarm);
|
||||
break;
|
||||
|
||||
case E_CAL_COMPONENT_ALARM_PROCEDURE:
|
||||
keep_in_reminders = reminder_watcher_notify_procedure (rw, rd, alarm);
|
||||
break;
|
||||
|
||||
case E_CAL_COMPONENT_ALARM_NONE:
|
||||
case E_CAL_COMPONENT_ALARM_UNKNOWN:
|
||||
break;
|
||||
}
|
||||
|
||||
e_cal_component_alarm_free (alarm);
|
||||
|
||||
return keep_in_reminders;
|
||||
}
|
||||
|
||||
static gpointer
|
||||
reminders_dismiss_thread (gpointer user_data)
|
||||
{
|
||||
ReminderWatcher *rw = user_data;
|
||||
EReminderWatcher *watcher;
|
||||
GSList *dismiss, *link;
|
||||
|
||||
g_return_val_if_fail (IS_REMINDER_WATCHER (rw), NULL);
|
||||
|
||||
g_mutex_lock (&rw->priv->dismiss_lock);
|
||||
dismiss = rw->priv->dismiss;
|
||||
rw->priv->dismiss = NULL;
|
||||
rw->priv->dismiss_thread = NULL;
|
||||
g_mutex_unlock (&rw->priv->dismiss_lock);
|
||||
|
||||
watcher = E_REMINDER_WATCHER (rw);
|
||||
if (watcher)
|
||||
{
|
||||
for (link = dismiss; link; link = g_slist_next (link))
|
||||
{
|
||||
EReminderData *rd = link->data;
|
||||
|
||||
if (rd)
|
||||
{
|
||||
/* Silently ignore any errors here */
|
||||
e_reminder_watcher_dismiss_sync (watcher, rd, NULL, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_slist_free_full (dismiss, e_reminder_data_free);
|
||||
g_clear_object (&rw);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
reminders_triggered_cb (EReminderWatcher *watcher,
|
||||
const GSList *reminders, /* EReminderData * */
|
||||
gboolean snoozed,
|
||||
gpointer user_data)
|
||||
{
|
||||
ReminderWatcher *rw = REMINDER_WATCHER (watcher);
|
||||
GSList *link;
|
||||
|
||||
g_return_if_fail (IS_REMINDER_WATCHER (rw));
|
||||
|
||||
g_mutex_lock (&rw->priv->dismiss_lock);
|
||||
|
||||
for (link = (GSList *) reminders; link; link = g_slist_next (link))
|
||||
{
|
||||
const EReminderData *rd = link->data;
|
||||
|
||||
if (rd && !reminders_process_one (rw, rd, snoozed))
|
||||
{
|
||||
rw->priv->dismiss = g_slist_prepend (rw->priv->dismiss, e_reminder_data_copy (rd));
|
||||
}
|
||||
}
|
||||
|
||||
if (rw->priv->dismiss && !rw->priv->dismiss_thread)
|
||||
{
|
||||
rw->priv->dismiss_thread = g_thread_new (NULL, reminders_dismiss_thread, g_object_ref (rw));
|
||||
g_warn_if_fail (rw->priv->dismiss_thread != NULL);
|
||||
if (rw->priv->dismiss_thread)
|
||||
g_thread_unref (rw->priv->dismiss_thread);
|
||||
}
|
||||
|
||||
g_mutex_unlock (&rw->priv->dismiss_lock);
|
||||
}
|
||||
|
||||
static EClient *
|
||||
reminder_watcher_cal_client_connect_sync (EReminderWatcher *watcher,
|
||||
ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
guint32 wait_for_connected_seconds,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
ReminderWatcher *reminder_watcher = REMINDER_WATCHER (watcher);
|
||||
|
||||
return calendar_sources_connect_client_sync (reminder_watcher->priv->sources, FALSE, source, source_type,
|
||||
wait_for_connected_seconds, cancellable, error);
|
||||
}
|
||||
|
||||
static void
|
||||
reminder_watcher_cal_client_connect (EReminderWatcher *watcher,
|
||||
ESource *source,
|
||||
ECalClientSourceType source_type,
|
||||
guint32 wait_for_connected_seconds,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
ReminderWatcher *reminder_watcher = REMINDER_WATCHER (watcher);
|
||||
|
||||
calendar_sources_connect_client (reminder_watcher->priv->sources, FALSE, source, source_type,
|
||||
wait_for_connected_seconds, cancellable, callback, user_data);
|
||||
}
|
||||
|
||||
static EClient *
|
||||
reminder_watcher_cal_client_connect_finish (EReminderWatcher *watcher,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
ReminderWatcher *reminder_watcher = REMINDER_WATCHER (watcher);
|
||||
|
||||
return calendar_sources_connect_client_finish (reminder_watcher->priv->sources, result, error);
|
||||
}
|
||||
|
||||
static void
|
||||
reminder_watcher_constructed (GObject *object)
|
||||
{
|
||||
ReminderWatcher *rw = REMINDER_WATCHER (object);
|
||||
|
||||
G_OBJECT_CLASS (reminder_watcher_parent_class)->constructed (object);
|
||||
|
||||
rw->priv->sources = calendar_sources_get ();
|
||||
|
||||
g_signal_connect (rw, "triggered",
|
||||
G_CALLBACK (reminders_triggered_cb), NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
reminder_watcher_finalize (GObject *object)
|
||||
{
|
||||
ReminderWatcher *rw = REMINDER_WATCHER (object);
|
||||
|
||||
g_clear_object (&rw->priv->sources);
|
||||
g_clear_object (&rw->priv->settings);
|
||||
g_mutex_clear (&rw->priv->dismiss_lock);
|
||||
|
||||
G_OBJECT_CLASS (reminder_watcher_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
reminder_watcher_class_init (ReminderWatcherClass *klass)
|
||||
{
|
||||
GObjectClass *object_class;
|
||||
EReminderWatcherClass *watcher_class;
|
||||
|
||||
object_class = G_OBJECT_CLASS (klass);
|
||||
object_class->constructed = reminder_watcher_constructed;
|
||||
object_class->finalize = reminder_watcher_finalize;
|
||||
|
||||
watcher_class = E_REMINDER_WATCHER_CLASS (klass);
|
||||
watcher_class->cal_client_connect_sync = reminder_watcher_cal_client_connect_sync;
|
||||
watcher_class->cal_client_connect = reminder_watcher_cal_client_connect;
|
||||
watcher_class->cal_client_connect_finish = reminder_watcher_cal_client_connect_finish;
|
||||
}
|
||||
|
||||
static void
|
||||
reminder_watcher_init (ReminderWatcher *rw)
|
||||
{
|
||||
rw->priv = reminder_watcher_get_instance_private (rw);
|
||||
rw->priv->settings = g_settings_new ("org.gnome.evolution-data-server.calendar");
|
||||
|
||||
g_mutex_init (&rw->priv->dismiss_lock);
|
||||
}
|
||||
|
||||
EReminderWatcher *
|
||||
reminder_watcher_new (GApplication *application,
|
||||
ESourceRegistry *registry)
|
||||
{
|
||||
ReminderWatcher *rw;
|
||||
|
||||
g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
|
||||
|
||||
rw = g_object_new (TYPE_REMINDER_WATCHER,
|
||||
"registry", registry,
|
||||
NULL);
|
||||
|
||||
rw->priv->application = application;
|
||||
|
||||
return E_REMINDER_WATCHER (rw);
|
||||
}
|
||||
|
||||
static void
|
||||
reminder_watcher_dismiss_done_cb (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GError *error = NULL;
|
||||
|
||||
if (!e_reminder_watcher_dismiss_finish (E_REMINDER_WATCHER (source_object), result, &error))
|
||||
{
|
||||
if (!g_error_matches (error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED))
|
||||
print_debug ("Dismiss: Failed with error: %s", error ? error->message : "Unknown error");
|
||||
|
||||
g_clear_error (&error);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
reminder_watcher_dismiss_by_id (EReminderWatcher *reminder_watcher,
|
||||
const gchar *id)
|
||||
{
|
||||
ReminderWatcher *rw;
|
||||
GSList *past, *link;
|
||||
|
||||
g_return_if_fail (IS_REMINDER_WATCHER (reminder_watcher));
|
||||
g_return_if_fail (id && *id);
|
||||
|
||||
rw = REMINDER_WATCHER (reminder_watcher);
|
||||
past = e_reminder_watcher_dup_past (reminder_watcher);
|
||||
|
||||
for (link = past; link; link = g_slist_next (link))
|
||||
{
|
||||
EReminderData *rd = link->data;
|
||||
gchar *rd_id;
|
||||
|
||||
rd_id = reminder_watcher_build_notif_id (rd);
|
||||
|
||||
if (g_strcmp0 (rd_id, id) == 0)
|
||||
{
|
||||
print_debug ("Dismiss: Going to dismiss '%s'", reminder_watcher_get_rd_summary (rd));
|
||||
|
||||
g_application_withdraw_notification (rw->priv->application, id);
|
||||
|
||||
e_reminder_watcher_dismiss (reminder_watcher, rd, NULL,
|
||||
reminder_watcher_dismiss_done_cb, NULL);
|
||||
|
||||
g_free (rd_id);
|
||||
break;
|
||||
}
|
||||
|
||||
g_free (rd_id);
|
||||
}
|
||||
|
||||
if (!link)
|
||||
print_debug ("Dismiss: Cannot find reminder '%s'", id);
|
||||
|
||||
g_slist_free_full (past, e_reminder_data_free);
|
||||
}
|
67
src/calendar-server/reminder-watcher.h
Normal file
67
src/calendar-server/reminder-watcher.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Red Hat (www.redhat.com)
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef REMINDER_WATCHER_H
|
||||
#define REMINDER_WATCHER_H
|
||||
|
||||
#define HANDLE_LIBICAL_MEMORY
|
||||
#define EDS_DISABLE_DEPRECATED
|
||||
#include <libecal/libecal.h>
|
||||
|
||||
/* Standard GObject macros */
|
||||
#define TYPE_REMINDER_WATCHER \
|
||||
(reminder_watcher_get_type ())
|
||||
#define REMINDER_WATCHER(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_CAST \
|
||||
((obj), TYPE_REMINDER_WATCHER, ReminderWatcher))
|
||||
#define REMINDER_WATCHER_CLASS(cls) \
|
||||
(G_TYPE_CHECK_CLASS_CAST \
|
||||
((cls), TYPE_REMINDER_WATCHER, ReminderWatcherClass))
|
||||
#define IS_REMINDER_WATCHER(obj) \
|
||||
(G_TYPE_CHECK_INSTANCE_TYPE \
|
||||
((obj), TYPE_REMINDER_WATCHER))
|
||||
#define IS_REMINDER_WATCHER_CLASS(cls) \
|
||||
(G_TYPE_CHECK_CLASS_TYPE \
|
||||
((cls), TYPE_REMINDER_WATCHER))
|
||||
#define REMINDER_WATCHER_GET_CLASS(obj) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS \
|
||||
((obj), TYPE_REMINDER_WATCHER, ReminderWatcherClass))
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct _ReminderWatcher ReminderWatcher;
|
||||
typedef struct _ReminderWatcherClass ReminderWatcherClass;
|
||||
typedef struct _ReminderWatcherPrivate ReminderWatcherPrivate;
|
||||
|
||||
struct _ReminderWatcher {
|
||||
EReminderWatcher parent;
|
||||
ReminderWatcherPrivate *priv;
|
||||
};
|
||||
|
||||
struct _ReminderWatcherClass {
|
||||
EReminderWatcherClass parent_class;
|
||||
};
|
||||
|
||||
GType reminder_watcher_get_type (void) G_GNUC_CONST;
|
||||
EReminderWatcher *reminder_watcher_new (GApplication *application,
|
||||
ESourceRegistry *registry);
|
||||
void reminder_watcher_dismiss_by_id(EReminderWatcher *reminder_watcher,
|
||||
const gchar *id);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* REMINDER_WATCHER_H */
|
Reference in New Issue
Block a user