8 Commits
v6 ... v13

7 changed files with 161 additions and 66 deletions

View File

@@ -7,6 +7,10 @@ This GNOME Shell Extension aims to display information to center box. Using DBUS
Installation Installation
------------ ------------
Install it from https://extensions.gnome.org/extension/2826/generic-monitor/
OR
Create a symbolic link from your _.local_ directory and enable extension Create a symbolic link from your _.local_ directory and enable extension
ln -s $PWD/generic-monitor@gnome-shell-extensions/ ~/.local/share/gnome-shell/extensions/ ln -s $PWD/generic-monitor@gnome-shell-extensions/ ~/.local/share/gnome-shell/extensions/
@@ -109,11 +113,11 @@ Signals can be :
Targets : Targets :
* signal : emit a signal to desktop application * signal : Emit a signal to desktop application
* delete : Delete item * delete : Delete item
* open-popup : Open the popup if there is one * open-popup : Open the popup if there is one
* close-popup : Close the popup if there is one * close-popup : Close the popup if there is one
* toggle-popup : Toggle (open/close) the popup if there is one * toggle-popup : Toggle (open/close) the popup if there is one
Signal names emit when action "signal" is specified : Signal names emit when action "signal" is specified :
@@ -142,11 +146,11 @@ Example
You can test it with command line : You can test it with command line :
gdbus call --session --dest org.gnome.Shell --object-path /com/soutade/GenericMonitor --method com.soutade.GenericMonitor.notify '{"group":"new","items":[{"name":"first","on-click":"toggle-popup","text":{"text":"Hello","style":"color:green"},"popup":{"items":[{"picture":{"path":"/tmp/cat2.jpg"}}]}}]}' gdbus call --session --dest org.gnome.Shell --object-path /com/soutade/GenericMonitor --method com.soutade.GenericMonitor.notify '{"group":"new","items":[{"name":"first","on-click":"toggle-popup","text":{"text":"Hello","style":"color:green"},"popup":{"items":[{"picture":{"path":"/tmp/cat.jpg"}}]}}]}'
gdbus call --session --dest org.gnome.Shell --object-path /com/soutade/GenericMonitor --method com.soutade.GenericMonitor.deleteGroups '{"groups":["new"]}' gdbus call --session --dest org.gnome.Shell --object-path /com/soutade/GenericMonitor --method com.soutade.GenericMonitor.deleteGroups '{"groups":["new"]}'
Python examples is available Python examples are available @ https://indefero.soutade.fr/p/genericmonitor/source/tree/master/examples
Development Development

View File

@@ -1,47 +1,47 @@
<interface name="com.soutade.GenericMonitor"> <interface name="com.soutade.GenericMonitor">
<!-- Functions --> <!-- Functions -->
<method name="notify"> <method name="notify">
<arg type="s" direction="in" /> <arg name="parameters" type="s" direction="in" />
</method> </method>
<method name="deleteItems"> <method name="deleteItems">
<arg type="s" direction="in" /> <arg name="items" type="s" direction="in" />
</method> </method>
<method name="deleteGroups"> <method name="deleteGroups">
<arg type="s" direction="in" /> <arg name="groups" type="s" direction="in" />
</method> </method>
<method name="openPopup"> <method name="openPopup">
<arg type="s" direction="in" /> <arg name="popup" type="s" direction="in" />
</method> </method>
<method name="closePopup"> <method name="closePopup">
<arg type="s" direction="in" /> <arg name="popup" type="s" direction="in" />
</method> </method>
<method name="togglePopup"> <method name="togglePopup">
<arg type="s" direction="in" /> <arg name="popup" type="s" direction="in" />
</method> </method>
<!-- Events --> <!-- Events -->
<signal name="onClick"> <signal name="onClick">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onRightClick"> <signal name="onRightClick">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onDblClick"> <signal name="onDblClick">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onRightDblClick"> <signal name="onRightDblClick">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onScrollUp"> <signal name="onScrollUp">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onScrollDown"> <signal name="onScrollDown">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onEnter"> <signal name="onEnter">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<signal name="onLeave"> <signal name="onLeave">
<arg type="s" direction="out" /> <arg name="fullName" type="s" direction="out" />
</signal> </signal>
<!-- Activate/Deactivate signals --> <!-- Activate/Deactivate signals -->
<signal name="onActivate"> <signal name="onActivate">

77
examples/gmail.py Normal file
View File

@@ -0,0 +1,77 @@
#
# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Setup : https://developers.google.com/gmail/api/quickstart/python
# Need to have credentials.json were you call main script
#
# File imported from https://github.com/googleworkspace/python-samples/blob/main/gmail/quickstart/quickstart.py
#
from __future__ import print_function
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
creds = None
def _initCreds():
global creds
if creds: return
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('./token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())
def getUnreadMails():
"""
Get number of unread threads (that may contain multiple messages)
"""
_initCreds()
service = build('gmail', 'v1', credentials=creds)
pageToken = ''
threads = set()
while True:
results = service.users().messages().list(userId='me', labelIds=['UNREAD'],\
includeSpamTrash=False, pageToken=pageToken)\
.execute()
threads = threads.union(set([k['threadId'] for k in results['messages']]))
# Loop over all result pages (100 results per page by default)
pageToken = results.get('nextPageToken', '')
if not pageToken: break
return len(threads)

View File

@@ -23,11 +23,11 @@ import time
import requests import requests
from requests.auth import HTTPBasicAuth from requests.auth import HTTPBasicAuth
import xml.dom.minidom import xml.dom.minidom
import getpass
from threading import Thread from threading import Thread
from signal import signal, SIGINT from signal import signal, SIGINT
import sys import sys
from genericmonitor import * from genericmonitor import *
from gmail import getUnreadMails
PURPLE_CONV_UPDATE_UNSEEN = 4 PURPLE_CONV_UPDATE_UNSEEN = 4
PURPLE_MESSAGE_SEND = 0 PURPLE_MESSAGE_SEND = 0
@@ -59,31 +59,23 @@ class PidginConversation:
class EventThread(Thread,GenericMonitor): class EventThread(Thread,GenericMonitor):
SLEEP_TIME = 30 SLEEP_TIME = 30
MAIL_ADDRESS='XXX@gmail.com'
def stop(self): def stop(self):
self._stopLoop = True self._stopLoop = True
self.stopMainLoop() self.stopMainLoop()
def _getMail(self): def _getMail(self):
address = "https://mail.google.com/mail/feed/atom"
auth = HTTPBasicAuth(self.MAIL_ADDRESS, self._mail_password)
req = requests.get(address, auth=auth)
text = '' text = ''
style = '' style = ''
if req.status_code == requests.codes.ok: try:
dom = xml.dom.minidom.parseString(req.text) nb_messages = getUnreadMails()
try: if nb_messages == 1:
nb_messages = int(dom.getElementsByTagName('fullcount')[0].firstChild.nodeValue) text = '1 msg'
if nb_messages == 1: elif nb_messages > 1:
text = '1 msg' text = '%d msgs' % (nb_messages)
elif nb_messages > 1: style = 'color:white'
text = '%d msgs' % (nb_messages) except Exception as e:
style = 'color:white' text = str(e)
except Exception as e:
text = str(e)
else:
text = 'Mail error %d' % (req.status_code)
self.mailWidget.setText(text) self.mailWidget.setText(text)
self.mailWidget.setStyle(style) self.mailWidget.setStyle(style)
@@ -103,8 +95,6 @@ class EventThread(Thread,GenericMonitor):
self.add_signal_receiver(self.pidginMessageWrote, 'WroteChatMsg', 'im.pidgin.purple.PurpleInterface') self.add_signal_receiver(self.pidginMessageWrote, 'WroteChatMsg', 'im.pidgin.purple.PurpleInterface')
self.add_signal_receiver(self.pidginConversationUpdated, 'ConversationUpdated', 'im.pidgin.purple.PurpleInterface') self.add_signal_receiver(self.pidginConversationUpdated, 'ConversationUpdated', 'im.pidgin.purple.PurpleInterface')
self._mail_password = getpass.getpass('Enter password for address %s: ' % (self.MAIL_ADDRESS))
self.mailWidget = GenericMonitorTextWidget('') self.mailWidget = GenericMonitorTextWidget('')
mailItem = GenericMonitorItem('mail', [self.mailWidget]) mailItem = GenericMonitorItem('mail', [self.mailWidget])
self.mailGroup = GenericMonitorGroup('Mail', [mailItem]) self.mailGroup = GenericMonitorGroup('Mail', [mailItem])

View File

@@ -56,7 +56,7 @@ class PicturePopup(GenericMonitor):
popup = GenericMonitorPopup([url_widget, picture_widget]) popup = GenericMonitorPopup([url_widget, picture_widget])
signals = { signals = {
'on-click':'toggle-popup', 'on-click':'toggle-popup',
# Could also use this behavior # Could also use this behavior [bugged since GNOME 42]
# 'on-enter':'open-popup', # 'on-enter':'open-popup',
# 'on-leave':'close-popup', # 'on-leave':'close-popup',
'on-dblclick':'signal', 'on-dblclick':'signal',

View File

@@ -24,9 +24,9 @@
https://github.com/bananenfisch/RecentItems/blob/master/extension.js https://github.com/bananenfisch/RecentItems/blob/master/extension.js
https://github.com/julio641742/gnome-shell-extension-reference/blob/master/tutorials/POPUPMENU-EXTENSION.md https://github.com/julio641742/gnome-shell-extension-reference/blob/master/tutorials/POPUPMENU-EXTENSION.md
https://gjs-docs.gnome.org/st10~1.0_api/st.widget https://gjs-docs.gnome.org/st10~1.0_api/st.widget
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/master/js/ui/panelMenu.js https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/panelMenu.js
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/master/js/ui/popupMenu.js https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/popupMenu.js
https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/master/js/ui/panel.js https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/panel.js
*/ */
const St = imports.gi.St; const St = imports.gi.St;
@@ -56,16 +56,17 @@ function log(message) {
class SignalMgt { class SignalMgt {
constructor(item, name, group, dbus, menu) { constructor(item, name, group, dbus, buttonMenu) {
this.name = name; this.name = name;
this.group = group; this.group = group;
this.fullname = this.name + '@' + this.group; this.fullname = this.name + '@' + this.group;
this.dbus = dbus; this.dbus = dbus;
this.menu = menu; this.buttonMenu = buttonMenu;
this.signals = new WeakMap(); this.signals = new WeakMap();
this.widgets = new Array(); this.widgets = new Array();
this.timeouts = new Array(); this.timeouts = new Array();
this.menuOpen = false;
this.nbClicks = 0; this.nbClicks = 0;
this.button = -1; this.button = -1;
@@ -118,25 +119,42 @@ class SignalMgt {
this.signals.set(widget, null); this.signals.set(widget, null);
} }
toggleMenu() {
if (this.menuOpen)
{
this.buttonMenu.menu.close();
this.menuOpen = false;
}
else
{
this.buttonMenu.menu.open(true);
this.menuOpen = true;
}
}
_manageEventAction(action, signalName) { _manageEventAction(action, signalName) {
if (action === 'open-popup') if (action === 'open-popup')
this.menu.open(true); {
this.buttonMenu.menu.open(true);
this.menuOpen = true;
}
else if (action === 'close-popup') else if (action === 'close-popup')
this.menu.close(); {
this.buttonMenu.menu.close();
this.menuOpen = false;
}
else if (action === 'toggle-popup') else if (action === 'toggle-popup')
this.menu.toggle(); {
else if (action == 'delete') this.toggleMenu();
}
else if (action === 'delete')
this.dbus.deleteItem(this, this.group); this.dbus.deleteItem(this, this.group);
else if (action === 'signal') else if (action === 'signal')
this.dbus.emitSignal(signalName, this.fullname); this.dbus.emitSignal(signalName, this.fullname);
return Clutter.EVENT_PROPAGATE; return Clutter.EVENT_STOP;
} }
_manageLeaveEvent() {
this._manageEventAction(this.onLeave);
}
_doClickCallback() { _doClickCallback() {
let right = ''; let right = '';
let nbClicks = ''; let nbClicks = '';
@@ -229,7 +247,8 @@ class MonitorWidget extends PanelMenu.Button {
this.group = group; this.group = group;
this.fullname = this.name + '@' + this.group; this.fullname = this.name + '@' + this.group;
this.dbus = dbus; this.dbus = dbus;
this.signalManager = new SignalMgt(item, this.name, group, dbus, this.menu); this.menuItem = null;
this.signalManager = new SignalMgt(item, this.name, group, dbus, this);
this.popup_signals = null; this.popup_signals = null;
this.popup_widgets = null; this.popup_widgets = null;
@@ -414,7 +433,7 @@ class MonitorWidget extends PanelMenu.Button {
const name = hashGet(nestedItem, 'name', ''); const name = hashGet(nestedItem, 'name', '');
this.popup_signals[widget] = new SignalMgt(nestedItem, name, this.popup_signals[widget] = new SignalMgt(nestedItem, name,
this.fullname, this.dbus, this.fullname, this.dbus,
this.menu); this);
this.popup_signals[widget].connectWidgetSignals(widget); this.popup_signals[widget].connectWidgetSignals(widget);
this.popup_widgets.push(widget); this.popup_widgets.push(widget);
} }
@@ -791,15 +810,19 @@ class Extension {
} }
} }
const extension = new Extension(); let extension = null;
function init() { function init() {
} }
function enable() { function enable() {
extension = new Extension();
extension.enable(); extension.enable();
} }
function disable() { function disable() {
extension.disable(); if (extension) {
extension.disable();
extension = null;
}
} }

View File

@@ -2,12 +2,13 @@
"uuid": "generic-monitor@gnome-shell-extensions", "uuid": "generic-monitor@gnome-shell-extensions",
"name": "Generic Monitor", "name": "Generic Monitor",
"description": "Display text & icon on systray using DBUS", "description": "Display text & icon on systray using DBUS",
"version": "6", "version": "13",
"shell-version": [ "shell-version": [
"41", "44",
"40", "43",
"3.38", "42.3",
"3.36" "42",
"41"
], ],
"url": "http://indefero.soutade.fr/p/genericmonitor" "url": "http://indefero.soutade.fr/p/genericmonitor"
} }