python实现桌面气泡提示功能


Posted in Python onJuly 29, 2019

在写桌面软件时,通常会使用到托盘上的泡泡提示功能,让我们来看看使用python如何实现这个小功能。

一、Linux系统

在Linux上,实现一个气泡提示非常简单,使用GTK实现的pynotify模块提供了些功能,我的环境是Ubuntu,默认安装此模块,如果没有,下载源文件编译安装一个。实现代码如下:

#!/usr/bin/python
#coding:utf-8
 
import pynotify
 
pynotify.init ("Bubble@Linux")
bubble_notify = pynotify.Notification ("Linux上的泡泡提示", "看,比Windows上实现方便多了!")
bubble_notify.show ()

效果:

python实现桌面气泡提示功能

二、Windows下的实现

Windows下实现是比较复杂的,没有pynotify这样一个模块,找到了一个还算不错的模块(地址),这个类有些语法上的小问题,至少在python2.6下如此,需要修改一下,如下是修改后的代码),基本可用,代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
#gtkPopupNotify.py
#
# Copyright 2009 Daniel Woodhouse 
# modified by NickCis 2010 http://github.com/NickCis/gtkPopupNotify
# Modifications:
#     Added: * Corner support (notifications can be displayed in all corners
#        * Use of gtk Stock items or pixbuf to render images in notifications
#        * Posibility of use fixed height
#        * Posibility of use image as background
#        * Not displaying over Windows taskbar(taken from emesene gpl v3)
#        * y separation.
#        * font description options
#        * Callbacks For left, middle and right click
#
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Lesser General Public License as published by
#the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
#
#You should have received a copy of the GNU Lesser General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 
import os
import gtk
import pango
import gobject
 
# This code is used only on Windows to get the location on the taskbar
# Taken from emesene Notifications (Gpl v3)
taskbarOffsety = 0
taskbarOffsetx = 0
if os.name == "nt":
  import ctypes
  from ctypes.wintypes import RECT, DWORD
  user = ctypes.windll.user32
  MONITORINFOF_PRIMARY = 1
  HMONITOR = 1
 
  class MONITORINFO(ctypes.Structure):
    _fields_ = [
      ('cbSize', DWORD),
      ('rcMonitor', RECT),
      ('rcWork', RECT),
      ('dwFlags', DWORD)
      ]
 
  taskbarSide = "bottom"
  taskbarOffset = 30
  info = MONITORINFO()
  info.cbSize = ctypes.sizeof(info)
  info.dwFlags = MONITORINFOF_PRIMARY
  user.GetMonitorInfoW(HMONITOR, ctypes.byref(info))
  if info.rcMonitor.bottom != info.rcWork.bottom:
    taskbarOffsety = info.rcMonitor.bottom - info.rcWork.bottom
  if info.rcMonitor.top != info.rcWork.top:
    taskbarSide = "top"
    taskbarOffsety = info.rcWork.top - info.rcMonitor.top
  if info.rcMonitor.left != info.rcWork.left:
    taskbarSide = "left"
    taskbarOffsetx = info.rcWork.left - info.rcMonitor.left
  if info.rcMonitor.right != info.rcWork.right:
    taskbarSide = "right"
    taskbarOffsetx = info.rcMonitor.right - info.rcWork.right
 
class NotificationStack:
  
  def __init__(self, size_x=300, size_y=-1, timeout=5, corner=(False, False), sep_y=0):
    """
    Create a new notification stack. The recommended way to create Popup instances.
     Parameters:
      `size_x` : The desired width of the notifications.
      `size_y` : The desired minimum height of the notifications. If it isn't set,
      or setted to None, the size will automatically adjust
      `timeout` : Popup instance will disappear after this timeout if there
      is no human intervention. This can be overridden temporarily by passing
      a new timout to the new_popup method.
      `coner` : 2 Value tuple: (true if left, True if top)
      `sep_y` : y distance to separate notifications from each other
    """
    self.size_x = size_x
    self.size_y = -1 
    if (size_y == None): 
      pass
    else:
       size_y
    self.timeout = timeout
    self.corner = corner
    self.sep_y = sep_y
    """
    Other parameters:
    These will take effect for every popup created after the change.
      
      `edge_offset_y` : distance from the bottom of the screen and
      the bottom of the stack.
      `edge_offset_x` : distance from the right edge of the screen and
      the side of the stack.
      `max_popups` : The maximum number of popups to be shown on the screen
      at one time.
      `bg_color` : if None default is used (usually grey). set with a gtk.gdk.Color.
      `bg_pixmap` : Pixmap to use as background of notification. You can set a gtk.gdk.Pixmap
      or a path to a image. If none, the color background will be displayed.
      `bg_mask` : If a gtk.gdk.pixmap is specified under bg_pixmap, the mask of the pixmap has to be setted here.
      `fg_color` : if None default is used (usually black). set with a gtk.gdk.Color.
      `show_timeout` : if True, a countdown till destruction will be displayed.
      `close_but` : if True, the close button will be displayed.
      `fontdesc` : a 3 value Tuple containing the pango.FontDescriptions of the Header, message and counter
       (in that order). If a string is suplyed, it will be used for the 3 the same FontDescription.
       http://doc.stoq.com.br/devel/pygtk/class-pangofontdescription.html
    """    
    self.edge_offset_x = 0
    self.edge_offset_y = 0
    self.max_popups = 5
    self.fg_color = None
    self.bg_color = None
    self.bg_pixmap = None
    self.bg_mask = None
    self.show_timeout = False
    self.close_but = True
    self.fontdesc = ("Sans Bold 14", "Sans 12", "Sans 10")
    
    self._notify_stack = []
    self._offset = 0
 
    
  def new_popup(self, title, message, image=None, leftCb=None, middleCb=None, rightCb=None):
    """Create a new Popup instance."""
    if len(self._notify_stack) == self.max_popups:
      self._notify_stack[0].hide_notification()
    self._notify_stack.append(Popup(self, title, message, image, leftCb, middleCb, rightCb))
    self._offset += self._notify_stack[-1].y
    
  def destroy_popup_cb(self, popup):
    self._notify_stack.remove(popup)
    #move popups down if required
    offset = 0
    for note in self._notify_stack:
      offset = note.reposition(offset, self)
    self._offset = offset
  
  
 
  
class Popup(gtk.Window):
  def __init__(self, stack, title, message, image, leftCb, middleCb, rightCb):
    gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)
    
    self.leftclickCB = leftCb
    self.middleclickCB = middleCb
    self.rightclickCB = rightCb    
    
    self.set_size_request(stack.size_x, stack.size_y)
    self.set_decorated(False)
    self.set_deletable(False)
    self.set_property("skip-pager-hint", True)
    self.set_property("skip-taskbar-hint", True)
    self.connect("enter-notify-event", self.on_hover, True)
    self.connect("leave-notify-event", self.on_hover, False)
    self.set_opacity(0.2)
    self.destroy_cb = stack.destroy_popup_cb
    
    if type(stack.fontdesc) == tuple or type(stack.fontdesc) == list:
      fontH, fontM, fontC = stack.fontdesc
    else:
      fontH = fontM = fontC = stack.fontdesc
    
    main_box = gtk.VBox()
    header_box = gtk.HBox()
    self.header = gtk.Label()
    self.header.set_markup("<b>%s</b>" % title)
    self.header.set_padding(3, 3)
    self.header.set_alignment(0, 0)
    try:
      self.header.modify_font(pango.FontDescription(fontH))
    except Exception, e:
      print e
    header_box.pack_start(self.header, True, True, 5)
    if stack.close_but:
      close_button = gtk.Image()
    
      close_button.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
      close_button.set_padding(3, 3)
      close_window = gtk.EventBox()
      close_window.set_visible_window(False)
      close_window.connect("button-press-event", self.hide_notification)
      close_window.add(close_button)
      header_box.pack_end(close_window, False, False)
    main_box.pack_start(header_box)
    
    body_box = gtk.HBox()
    if image is not None:
      self.image = gtk.Image()
      self.image.set_size_request(70, 70)
      self.image.set_alignment(0, 0)
      if image in gtk.stock_list_ids():
        self.image.set_from_stock(image, gtk.ICON_SIZE_DIALOG)
      elif type(image) == gtk.gdk.Pixbuf:
        self.image.set_from_pixbuf(image)
      else:
        self.image.set_from_file(image)
      body_box.pack_start(self.image, False, False, 5)
    self.message = gtk.Label()
    self.message.set_property("wrap", True)
    self.message.set_size_request(stack.size_x - 90, -1)
    self.message.set_alignment(0, 0)
    self.message.set_padding(5, 10)
    self.message.set_markup(message)
    try:
      self.message.modify_font(pango.FontDescription(fontM))
    except Exception, e:
      print e
    self.counter = gtk.Label()
    self.counter.set_alignment(1, 1)
    self.counter.set_padding(3, 3)
    try:
      self.counter.modify_font(pango.FontDescription(fontC))
    except Exception, e:
      print e
    self.timeout = stack.timeout
    
    body_box.pack_start(self.message, True, False, 5)
    body_box.pack_end(self.counter, False, False, 5)
    main_box.pack_start(body_box)
    eventbox = gtk.EventBox()
    eventbox.set_property('visible-window', False)
    eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)
    eventbox.connect("button_press_event", self.onClick) 
    eventbox.add(main_box)
    self.add(eventbox)
    if stack.bg_pixmap is not None:
      if not type(stack.bg_pixmap) == gtk.gdk.Pixmap:
        stack.bg_pixmap, stack.bg_mask = gtk.gdk.pixbuf_new_from_file(stack.bg_pixmap).render_pixmap_and_mask()
      self.set_app_paintable(True)
      self.connect_after("realize", self.callbackrealize, stack.bg_pixmap, stack.bg_mask)
    elif stack.bg_color is not None:
      self.modify_bg(gtk.STATE_NORMAL, stack.bg_color)
    if stack.fg_color is not None:
      self.message.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
      self.header.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
      self.counter.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
    self.show_timeout = stack.show_timeout
    self.hover = False
    self.show_all()
    self.x, self.y = self.size_request()
    #Not displaying over windows bar 
    if os.name == 'nt':
      if stack.corner[0] and taskbarSide == "left":
        stack.edge_offset_x += taskbarOffsetx
      elif not stack.corner[0] and taskbarSide == 'right':
        stack.edge_offset_x += taskbarOffsetx
      if stack.corner[1] and taskbarSide == "top":
        stack.edge_offset_x += taskbarOffsety
      elif not stack.corner[1] and taskbarSide == 'bottom':
        stack.edge_offset_x += taskbarOffsety
        
    if stack.corner[0]:
      posx = stack.edge_offset_x
    else:
      posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x
    sep_y = 0 
    if (stack._offset == 0):
      pass
    else:
       stack.sep_y
    self.y += sep_y
    if stack.corner[1]:
      posy = stack._offset + stack.edge_offset_y + sep_y
    else:
      posy = gtk.gdk.screen_height()- self.y - stack._offset - stack.edge_offset_y
    self.move(posx, posy)
    self.fade_in_timer = gobject.timeout_add(100, self.fade_in)
    
    
 
  def reposition(self, offset, stack):
    """Move the notification window down, when an older notification is removed"""
    if stack.corner[0]:
      posx = stack.edge_offset_x
    else:
      posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x
    if stack.corner[1]:
      posy = offset + stack.edge_offset_y
      new_offset = self.y + offset
    else:      
      new_offset = self.y + offset
      posy = gtk.gdk.screen_height() - new_offset - stack.edge_offset_y + stack.sep_y
    self.move(posx, posy)
    return new_offset
 
  
  def fade_in(self):
    opacity = self.get_opacity()
    opacity += 0.15
    if opacity >= 1:
      self.wait_timer = gobject.timeout_add(1000, self.wait)
      return False
    self.set_opacity(opacity)
    return True
      
  def wait(self):
    if not self.hover:
      self.timeout -= 1
    if self.show_timeout:
      self.counter.set_markup(str("<b>%s</b>" % self.timeout))
    if self.timeout == 0:
      self.fade_out_timer = gobject.timeout_add(100, self.fade_out)
      return False
    return True
   
  
  def fade_out(self):
    opacity = self.get_opacity()
    opacity -= 0.10
    if opacity <= 0:
      self.in_progress = False
      self.hide_notification()
      return False
    self.set_opacity(opacity)
    return True
  
  def on_hover(self, window, event, hover):
    """Starts/Stops the notification timer on a mouse in/out event"""
    self.hover = hover
 
    
  def hide_notification(self, *args):
    """Destroys the notification and tells the stack to move the
    remaining notification windows"""
    for timer in ("fade_in_timer", "fade_out_timer", "wait_timer"):
      if hasattr(self, timer):
        gobject.source_remove(getattr(self, timer))
    self.destroy()
    self.destroy_cb(self)
 
  def callbackrealize(self, widget, pixmap, mask=False):
    #width, height = pixmap.get_size()
    #self.resize(width, height)
    if mask is not False:
      self.shape_combine_mask(mask, 0, 0)
    self.window.set_back_pixmap(pixmap, False)
    return True
 
  def onClick(self, widget, event):
    if event.button == 1 and self.leftclickCB != None:
      self.leftclickCB()
      self.hide_notification()
    if event.button == 2 and self.middleclickCB != None:
      self.middleclickCB()
      self.hide_notification()
    if event.button == 3 and self.rightclickCB != None:
      self.rightclickCB()
      self.hide_notification()
 
if __name__ == "__main__":
  #example usage
  
  def notify_factory():
    color = ("green", "blue")
    image = "logo1_64.png"
    notifier.bg_color = gtk.gdk.Color(color[0])
    notifier.fg_color = gtk.gdk.Color(color[1])
    notifier.show_timeout = True 
    notifier.edge_offset_x = 20
    notifier.edge_offset_y = 30
    notifier.new_popup("Windows上的泡泡提示", "NND,比Linux下复杂多了,效果还不怎么样", image=image)
    return True
 
  def gtk_main_quit():
    print "quitting"
    gtk.main_quit()
  
  notifier = NotificationStack(timeout=1) 
  gobject.timeout_add(4000, notify_factory)
  gobject.timeout_add(8000, gtk_main_quit)
  gtk.main()

效果如下:

python实现桌面气泡提示功能

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python中的yield使用方法
Feb 11 Python
浅析Python中signal包的使用
Nov 13 Python
详解在Python程序中解析并修改XML内容的方法
Nov 16 Python
Python 中urls.py:URL dispatcher(路由配置文件)详解
Mar 24 Python
Python实现的栈(Stack)
Jan 26 Python
python实现字符串加密 生成唯一固定长度字符串
Mar 22 Python
Python3匿名函数lambda介绍与使用示例
May 18 Python
pytest中文文档之编写断言
Sep 12 Python
Python 脚本的三种执行方式小结
Dec 21 Python
通过Turtle库在Python中绘制一个鼠年福鼠
Feb 03 Python
python连接打印机实现打印文档、图片、pdf文件等功能
Feb 07 Python
python中not、and和or的优先级与详细用法介绍
Nov 03 Python
pycharm设置鼠标悬停查看方法设置
Jul 29 #Python
django rest framework vue 实现用户登录详解
Jul 29 #Python
python实现倒计时小工具
Jul 29 #Python
django rest framework 实现用户登录认证详解
Jul 29 #Python
pycharm重命名文件的方法步骤
Jul 29 #Python
PyQt5实现暗黑风格的计时器
Jul 29 #Python
Python Django 实现简单注册功能过程详解
Jul 29 #Python
You might like
模仿OSO的论坛(五)
2006/10/09 PHP
php实现html标签闭合检测与修复方法
2015/07/09 PHP
PHP Socket网络操作类定义与用法示例
2017/08/30 PHP
Laravel学习教程之View模块详解
2017/09/18 PHP
php微信开发之音乐回复功能
2018/06/14 PHP
通过JAVASCRIPT读取ASP设定的COOKIE
2007/02/15 Javascript
两个比较有用的Javascript工具函数代码
2010/02/17 Javascript
JavaScript Perfection kill 测试及答案
2010/03/23 Javascript
jquery 弹出层注册页面等(asp.net后台)
2010/06/17 Javascript
jQuery实现鼠标可拖动调整表格列宽度
2014/05/26 Javascript
Jquery 实现图片轮换
2015/01/28 Javascript
JQUERY表单暂存功能插件分享
2016/02/23 Javascript
AngularJS变量及过滤器Filter用法分析
2016/11/22 Javascript
Ajax 加载数据 练习代码
2017/01/05 Javascript
简单谈谈js的数据类型
2017/09/25 Javascript
jQuery选择器之子元素过滤选择器
2017/09/28 jQuery
用Axios Element实现全局的请求loading的方法
2018/03/15 Javascript
nodejs一个简单的文件服务器的创建方法
2019/09/13 NodeJs
javascript开发实现贪吃蛇游戏
2020/07/31 Javascript
JavaScript常用进制转换及位运算实例解析
2020/10/14 Javascript
[01:00:25]2018DOTA2亚洲邀请赛3月30日 小组赛A组 VG VS Liquid
2018/03/31 DOTA
python类和继承用法实例
2015/07/07 Python
Python实现的密码强度检测器示例
2017/08/23 Python
详解python中的线程
2018/02/10 Python
python3.x+pyqt5实现主窗口状态栏里(嵌入)显示进度条功能
2019/07/04 Python
基于Python解密仿射密码
2019/10/21 Python
keras 自定义loss损失函数,sample在loss上的加权和metric详解
2020/05/23 Python
pycharm 实现本地写代码,服务器运行的操作
2020/06/08 Python
编写python代码实现简单抽奖器
2020/10/20 Python
python3爬虫中多线程进行解锁操作实例
2020/11/25 Python
python线程优先级队列知识点总结
2021/02/28 Python
处理textarea中的换行和空格
2019/12/12 HTML / CSS
新加坡第一的杂货零售商:NTUC FairPrice
2020/12/05 全球购物
介绍下Lucene建立索引的过程
2016/03/02 面试题
老龙头导游词
2015/02/11 职场文书
Python办公自动化之教你如何用Python将任意文件转为PDF格式
2021/06/28 Python