歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Python:桌面氣泡提示功能實現

Python:桌面氣泡提示功能實現

日期:2017/3/1 10:12:50   编辑:Linux編程

在寫桌面軟件時,通常會使用到托盤上的泡泡提示功能,讓我們來看看使用python如何實現這個小功能。

一、Linux系統:

在Linux上,實現一個氣泡提示非常簡單,使用GTK實現的pynotify模塊提供了些功能,我的環境是Ubuntu,默認安裝此模塊,如果沒有,可從http://home.gna.org/py-notify/下載源文件編譯安裝一個。實現代碼如下:

  1. #!/usr/bin/python
  2. #coding:utf-8
  3. import pynotify
  4. pynotify.init ("Bubble@Linux")
  5. bubble_notify = pynotify.Notification ("Linux上的泡泡提示", "看,比Windows上實現方便多了!")
  6. bubble_notify.show ()

效果:

二、Windows下的實現。

Windows下實現是比較復雜的,沒有pynotify這樣一個模塊,找到了一個還算不錯的模塊(地址:https://github.com/woodenbrick/gtkPopupNotify,這個類有些語法上的小問題,至少在python2.6下如此,需要修改一下,如下是修改後的代碼),基本可用,代碼如下:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #gtkPopupNotify.py
  4. #
  5. # Copyright 2009 Daniel Woodhouse
  6. # modified by NickCis 2010 http://github.com/NickCis/gtkPopupNotify
  7. # Modifications:
  8. # Added: * Corner support (notifications can be displayed in all corners
  9. # * Use of gtk Stock items or pixbuf to render images in notifications
  10. # * Posibility of use fixed height
  11. # * Posibility of use image as background
  12. # * Not displaying over Windows taskbar(taken from emesene gpl v3)
  13. # * y separation.
  14. # * font description options
  15. # * Callbacks For left, middle and right click
  16. #
  17. #This program is free software: you can redistribute it and/or modify
  18. #it under the terms of the GNU Lesser General Public License as published by
  19. #the Free Software Foundation, either version 3 of the License, or
  20. #(at your option) any later version.
  21. #
  22. #This program is distributed in the hope that it will be useful,
  23. #but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. #GNU Lesser General Public License for more details.
  26. #
  27. #You should have received a copy of the GNU Lesser General Public License
  28. #along with this program. If not, see <http://www.gnu.org/licenses/>.
  29. import os
  30. import gtk
  31. import pango
  32. import gobject
  33. # This code is used only on Windows to get the location on the taskbar
  34. # Taken from emesene Notifications (Gpl v3)
  35. taskbarOffsety = 0
  36. taskbarOffsetx = 0
  37. if os.name == "nt":
  38. import ctypes
  39. from ctypes.wintypes import RECT, DWORD
  40. user = ctypes.windll.user32
  41. MONITORINFOF_PRIMARY = 1
  42. HMONITOR = 1
  43. class MONITORINFO(ctypes.Structure):
  44. _fields_ = [
  45. ('cbSize', DWORD),
  46. ('rcMonitor', RECT),
  47. ('rcWork', RECT),
  48. ('dwFlags', DWORD)
  49. ]
  50. taskbarSide = "bottom"
  51. taskbarOffset = 30
  52. info = MONITORINFO()
  53. info.cbSize = ctypes.sizeof(info)
  54. info.dwFlags = MONITORINFOF_PRIMARY
  55. user.GetMonitorInfoW(HMONITOR, ctypes.byref(info))
  56. if info.rcMonitor.bottom != info.rcWork.bottom:
  57. taskbarOffsety = info.rcMonitor.bottom - info.rcWork.bottom
  58. if info.rcMonitor.top != info.rcWork.top:
  59. taskbarSide = "top"
  60. taskbarOffsety = info.rcWork.top - info.rcMonitor.top
  61. if info.rcMonitor.left != info.rcWork.left:
  62. taskbarSide = "left"
  63. taskbarOffsetx = info.rcWork.left - info.rcMonitor.left
  64. if info.rcMonitor.right != info.rcWork.right:
  65. taskbarSide = "right"
  66. taskbarOffsetx = info.rcMonitor.right - info.rcWork.right
  67. class NotificationStack:
  68. def __init__(self, size_x=300, size_y=-1, timeout=5, corner=(False, False), sep_y=0):
  69. """
  70. Create a new notification stack. The recommended way to create Popup instances.
  71. Parameters:
  72. `size_x` : The desired width of the notifications.
  73. `size_y` : The desired minimum height of the notifications. If it isn't set,
  74. or setted to None, the size will automatically adjust
  75. `timeout` : Popup instance will disappear after this timeout if there
  76. is no human intervention. This can be overridden temporarily by passing
  77. a new timout to the new_popup method.
  78. `coner` : 2 Value tuple: (true if left, True if top)
  79. `sep_y` : y distance to separate notifications from each other
  80. """
  81. self.size_x = size_x
  82. self.size_y = -1
  83. if (size_y == None):
  84. pass
  85. else:
  86. size_y
  87. self.timeout = timeout
  88. self.corner = corner
  89. self.sep_y = sep_y
  90. """
  91. Other parameters:
  92. These will take effect for every popup created after the change.
  93. `edge_offset_y` : distance from the bottom of the screen and
  94. the bottom of the stack.
  95. `edge_offset_x` : distance from the right edge of the screen and
  96. the side of the stack.
  97. `max_popups` : The maximum number of popups to be shown on the screen
  98. at one time.
  99. `bg_color` : if None default is used (usually grey). set with a gtk.gdk.Color.
  100. `bg_pixmap` : Pixmap to use as background of notification. You can set a gtk.gdk.Pixmap
  101. or a path to a image. If none, the color background will be displayed.
  102. `bg_mask` : If a gtk.gdk.pixmap is specified under bg_pixmap, the mask of the pixmap has to be setted here.
  103. `fg_color` : if None default is used (usually black). set with a gtk.gdk.Color.
  104. `show_timeout` : if True, a countdown till destruction will be displayed.
  105. `close_but` : if True, the close button will be displayed.
  106. `fontdesc` : a 3 value Tuple containing the pango.FontDescriptions of the Header, message and counter
  107. (in that order). If a string is suplyed, it will be used for the 3 the same FontDescription.
  108. http://doc.stoq.com.br/devel/pygtk/class-pangofontdescription.html
  109. """
  110. self.edge_offset_x = 0
  111. self.edge_offset_y = 0
  112. self.max_popups = 5
  113. self.fg_color = None
  114. self.bg_color = None
  115. self.bg_pixmap = None
  116. self.bg_mask = None
  117. self.show_timeout = False
  118. self.close_but = True
  119. self.fontdesc = ("Sans Bold 14", "Sans 12", "Sans 10")
  120. self._notify_stack = []
  121. self._offset = 0
  122. def new_popup(self, title, message, image=None, leftCb=None, middleCb=None, rightCb=None):
  123. """Create a new Popup instance."""
  124. if len(self._notify_stack) == self.max_popups:
  125. self._notify_stack[0].hide_notification()
  126. self._notify_stack.append(Popup(self, title, message, image, leftCb, middleCb, rightCb))
  127. self._offset += self._notify_stack[-1].y
  128. def destroy_popup_cb(self, popup):
  129. self._notify_stack.remove(popup)
  130. #move popups down if required
  131. offset = 0
  132. for note in self._notify_stack:
  133. offset = note.reposition(offset, self)
  134. self._offset = offset
  135. class Popup(gtk.Window):
  136. def __init__(self, stack, title, message, image, leftCb, middleCb, rightCb):
  137. gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)
  138. self.leftclickCB = leftCb
  139. self.middleclickCB = middleCb
  140. self.rightclickCB = rightCb
  141. self.set_size_request(stack.size_x, stack.size_y)
  142. self.set_decorated(False)
  143. self.set_deletable(False)
  144. self.set_property("skip-pager-hint", True)
  145. self.set_property("skip-taskbar-hint", True)
  146. self.connect("enter-notify-event", self.on_hover, True)
  147. self.connect("leave-notify-event", self.on_hover, False)
  148. self.set_opacity(0.2)
  149. self.destroy_cb = stack.destroy_popup_cb
  150. if type(stack.fontdesc) == tuple or type(stack.fontdesc) == list:
  151. fontH, fontM, fontC = stack.fontdesc
  152. else:
  153. fontH = fontM = fontC = stack.fontdesc
  154. main_box = gtk.VBox()
  155. header_box = gtk.HBox()
  156. self.header = gtk.Label()
  157. self.header.set_markup("<b>%s</b>" % title)
  158. self.header.set_padding(3, 3)
  159. self.header.set_alignment(0, 0)
  160. try:
  161. self.header.modify_font(pango.FontDescription(fontH))
  162. except Exception, e:
  163. print e
  164. header_box.pack_start(self.header, True, True, 5)
  165. if stack.close_but:
  166. close_button = gtk.Image()
  167. close_button.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
  168. close_button.set_padding(3, 3)
  169. close_window = gtk.EventBox()
  170. close_window.set_visible_window(False)
  171. close_window.connect("button-press-event", self.hide_notification)
  172. close_window.add(close_button)
  173. header_box.pack_end(close_window, False, False)
  174. main_box.pack_start(header_box)
  175. body_box = gtk.HBox()
  176. if image is not None:
  177. self.image = gtk.Image()
  178. self.image.set_size_request(70, 70)
  179. self.image.set_alignment(0, 0)
  180. if image in gtk.stock_list_ids():
  181. self.image.set_from_stock(image, gtk.ICON_SIZE_DIALOG)
  182. elif type(image) == gtk.gdk.Pixbuf:
  183. self.image.set_from_pixbuf(image)
  184. else:
  185. self.image.set_from_file(image)
  186. body_box.pack_start(self.image, False, False, 5)
  187. self.message = gtk.Label()
  188. self.message.set_property("wrap", True)
  189. self.message.set_size_request(stack.size_x - 90, -1)
  190. self.message.set_alignment(0, 0)
  191. self.message.set_padding(5, 10)
  192. self.message.set_markup(message)
  193. try:
  194. self.message.modify_font(pango.FontDescription(fontM))
  195. except Exception, e:
  196. print e
  197. self.counter = gtk.Label()
  198. self.counter.set_alignment(1, 1)
  199. self.counter.set_padding(3, 3)
  200. try:
  201. self.counter.modify_font(pango.FontDescription(fontC))
  202. except Exception, e:
  203. print e
  204. self.timeout = stack.timeout
  205. body_box.pack_start(self.message, True, False, 5)
  206. body_box.pack_end(self.counter, False, False, 5)
  207. main_box.pack_start(body_box)
  208. eventbox = gtk.EventBox()
  209. eventbox.set_property('visible-window', False)
  210. eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)
  211. eventbox.connect("button_press_event", self.onClick)
  212. eventbox.add(main_box)
  213. self.add(eventbox)
  214. if stack.bg_pixmap is not None:
  215. if not type(stack.bg_pixmap) == gtk.gdk.Pixmap:
  216. stack.bg_pixmap, stack.bg_mask = gtk.gdk.pixbuf_new_from_file(stack.bg_pixmap).render_pixmap_and_mask()
  217. self.set_app_paintable(True)
  218. self.connect_after("realize", self.callbackrealize, stack.bg_pixmap, stack.bg_mask)
  219. elif stack.bg_color is not None:
  220. self.modify_bg(gtk.STATE_NORMAL, stack.bg_color)
  221. if stack.fg_color is not None:
  222. self.message.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
  223. self.header.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
  224. self.counter.modify_fg(gtk.STATE_NORMAL, stack.fg_color)
  225. self.show_timeout = stack.show_timeout
  226. self.hover = False
  227. self.show_all()
  228. self.x, self.y = self.size_request()
  229. #Not displaying over windows bar
  230. if os.name == 'nt':
  231. if stack.corner[0] and taskbarSide == "left":
  232. stack.edge_offset_x += taskbarOffsetx
  233. elif not stack.corner[0] and taskbarSide == 'right':
  234. stack.edge_offset_x += taskbarOffsetx
  235. if stack.corner[1] and taskbarSide == "top":
  236. stack.edge_offset_x += taskbarOffsety
  237. elif not stack.corner[1] and taskbarSide == 'bottom':
  238. stack.edge_offset_x += taskbarOffsety
  239. if stack.corner[0]:
  240. posx = stack.edge_offset_x
  241. else:
  242. posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x
  243. sep_y = 0
  244. if (stack._offset == 0):
  245. pass
  246. else:
  247. stack.sep_y
  248. self.y += sep_y
  249. if stack.corner[1]:
  250. posy = stack._offset + stack.edge_offset_y + sep_y
  251. else:
  252. posy = gtk.gdk.screen_height()- self.y - stack._offset - stack.edge_offset_y
  253. self.move(posx, posy)
  254. self.fade_in_timer = gobject.timeout_add(100, self.fade_in)
  255. def reposition(self, offset, stack):
  256. """Move the notification window down, when an older notification is removed"""
  257. if stack.corner[0]:
  258. posx = stack.edge_offset_x
  259. else:
  260. posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x
  261. if stack.corner[1]:
  262. posy = offset + stack.edge_offset_y
  263. new_offset = self.y + offset
  264. else:
  265. new_offset = self.y + offset
  266. posy = gtk.gdk.screen_height() - new_offset - stack.edge_offset_y + stack.sep_y
  267. self.move(posx, posy)
  268. return new_offset
  269. def fade_in(self):
  270. opacity = self.get_opacity()
  271. opacity += 0.15
  272. if opacity >= 1:
  273. self.wait_timer = gobject.timeout_add(1000, self.wait)
  274. return False
  275. self.set_opacity(opacity)
  276. return True
  277. def wait(self):
  278. if not self.hover:
  279. self.timeout -= 1
  280. if self.show_timeout:
  281. self.counter.set_markup(str("<b>%s</b>" % self.timeout))
  282. if self.timeout == 0:
  283. self.fade_out_timer = gobject.timeout_add(100, self.fade_out)
  284. return False
  285. return True
  286. def fade_out(self):
  287. opacity = self.get_opacity()
  288. opacity -= 0.10
  289. if opacity <= 0:
  290. self.in_progress = False
  291. self.hide_notification()
  292. return False
  293. self.set_opacity(opacity)
  294. return True
  295. def on_hover(self, window, event, hover):
  296. """Starts/Stops the notification timer on a mouse in/out event"""
  297. self.hover = hover
  298. def hide_notification(self, *args):
  299. """Destroys the notification and tells the stack to move the
  300. remaining notification windows"""
  301. for timer in ("fade_in_timer", "fade_out_timer", "wait_timer"):
  302. if hasattr(self, timer):
  303. gobject.source_remove(getattr(self, timer))
  304. self.destroy()
  305. self.destroy_cb(self)
  306. def callbackrealize(self, widget, pixmap, mask=False):
  307. #width, height = pixmap.get_size()
  308. #self.resize(width, height)
  309. if mask is not False:
  310. self.shape_combine_mask(mask, 0, 0)
  311. self.window.set_back_pixmap(pixmap, False)
  312. return True
  313. def onClick(self, widget, event):
  314. if event.button == 1 and self.leftclickCB != None:
  315. self.leftclickCB()
  316. self.hide_notification()
  317. if event.button == 2 and self.middleclickCB != None:
  318. self.middleclickCB()
  319. self.hide_notification()
  320. if event.button == 3 and self.rightclickCB != None:
  321. self.rightclickCB()
  322. self.hide_notification()
  323. if __name__ == "__main__":
  324. #example usage
  325. def notify_factory():
  326. color = ("green", "blue")
  327. image = "logo1_64.png"
  328. notifier.bg_color = gtk.gdk.Color(color[0])
  329. notifier.fg_color = gtk.gdk.Color(color[1])
  330. notifier.show_timeout = True
  331. notifier.edge_offset_x = 20
  332. notifier.edge_offset_y = 30
  333. notifier.new_popup("Windows上的泡泡提示", "NND,比Linux下復雜多了,效果還不怎麼樣", image=image)
  334. return True
  335. def gtk_main_quit():
  336. print "quitting"
  337. gtk.main_quit()
  338. notifier = NotificationStack(timeout=1)
  339. gobject.timeout_add(4000, notify_factory)
  340. gobject.timeout_add(8000, gtk_main_quit)
  341. gtk.main()

效果如下:

Copyright © Linux教程網 All Rights Reserved