2015-12-13 18 views
5

Trên Windows thật dễ dàng. Chỉ cần chạy chương trình của bạn với pythonw thay vì với python và mã sẽ được thực hiện trong nền.Python - Làm cách nào để tạo một daemon ra khỏi Ứng dụng GUI trên Mac OS X?

Vì vậy, điều tôi muốn đạt được dễ dàng được sắp xếp.

Tôi có một ứng dụng thực sự là dịch vụ làm công cụ ngầm. Nhưng dịch vụ này cần một bảng điều khiển. Vì vậy, trên Windows, tôi sử dụng wxPython để tạo GUI, thậm chí một số công cụ wx để cung cấp dịch vụ cần thiết, và khi người dùng thực hiện xong các điều chỉnh, nhấn Hide và Show (False) được gọi trên cửa sổ chính.

Do đó GUI biến mất và dịch vụ tiếp tục hoạt động trong nền. Người dùng luôn có thể mang lại bằng cách sử dụng phím nóng.

Sự cố là trên Mac OS X, chiến lược này chỉ hoạt động ở một mức độ nào đó.

Khi wx.Frame.Show (False) được gọi, cửa sổ biến mất cùng với thanh menu và dịch vụ hoạt động tốt, nhưng Ứng dụng vẫn hiển thị ở đó.

Bạn có thể chuyển sang bất kể thực tế là bạn không thể làm bất cứ điều gì với nó. Nó vẫn còn hiện diện trong Dock, v.v.

Điều này xảy ra khi chương trình đang sử dụng python hoặc pythonw hoặc khi nó được đóng gói với Py2App.

Bất kể tôi làm gì, biểu tượng vẫn ở đó.

Phải có một số thủ thuật cho phép một lập trình viên loại bỏ biểu tượng nghịch ngợm này và do đó không làm phiền người dùng kém khi họ không muốn bị làm phiền.

Ẩn cửa sổ hiển nhiên là không đủ. Có ai biết lừa không?

N.B .: Tôi thực sự muốn làm theo cách tôi mô tả ở trên và không gây rối với hai quy trình riêng biệt và IPC.

Edit:

Sau nhiều đào tôi thấy những:

How to hide application icon from Mac OS X dock

http://codesorcery.net/2008/02/06/feature-requests-versus-the-right-way-to-do-it

How to hide the Dock icon

Theo liên kết cuối cùng một cách thích hợp để làm điều đó là sử dụng:

[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory]; 

hoặc

[NSApp setActivationPolicy: NSApplicationActivationPolicyProhibited]; 

Vì vậy, những gì tôi muốn (runtime chuyển từ nền cho tiền cảnh và mặt sau) là có thể.

Nhưng cách thực hiện từ Python ???

Hằng số: NSApplicationActivationPolicyProhibited và NSApplicationActivationPolicyAccessory hiện diện trong AppKit, nhưng tôi không thể tìm thấy hàm setApplicationActivationPolicy ở bất kỳ đâu.

NSApp() không có.

Tôi biết có một cách để làm việc đó bằng cách tải dylib objc với ctypes, ủy thác cho NSApp và gửi "setApplicationActivationPolicy: <constant_value>", nhưng tôi không biết bao nhiêu sẽ mess này với wx.App() . Và nó là một chút công việc cho một cái gì đó mà nên có sẵn rồi.

Theo kinh nghiệm của tôi, NSApp() và wx.App() hoạt động đồng thời không thích eachother khá nhiều.

Có lẽ chúng ta có thể lấy dụ NSApp() mà wx đang sử dụng bằng cách nào đó và sử dụng ủy nhiệm của wx ???

Hãy nhớ xin vui lòng, các giải pháp đã đề xuất bắt đầu làm đại lý và chuyển sang tiền cảnh hoặc chạy nhiều quy trình và thực hiện IPC là rất không mong muốn trong trường hợp của tôi.

Vì vậy, lý tưởng, sử dụng setApplicationActivationPolicy là mục tiêu của tôi, nhưng làm cách nào? (Đơn giản và dễ dàng và không gây rối cho wx.App() vui lòng.)

Bất kỳ ý tưởng nào ???

+0

P.S Tại sao bạn không thực hiện giải pháp 'info.plist'? –

+0

Bởi vì điều đó có nghĩa là quá trình bắt đầu ở chế độ nền và bất kỳ GUI nào tôi muốn hiển thị đều OK, nhưng tôi sẽ không thể chuyển sang ứng dụng này sau khi tôi mất tiêu điểm, tức là biểu tượng Dock sẽ không xuất hiện. Điều đó có thể được sửa chữa bằng cách sử dụng TransformProcessType() mà tôi không biết làm thế nào để gọi từ Python một lần nữa, và nơi nó được ẩn, nếu nó được thực hiện ở tất cả. Nhưng, ngay cả khi tôi làm điều này, nó sẽ có nghĩa là tôi phải có 2 quy trình. Một daemon, vẫn giữ daemon và một GUI, gọi lại ứng dụng khi được yêu cầu và yêu cầu nó chạy như một daemon. – Dalen

+0

Nếu tôi làm điều này, tôi sẽ phải sử dụng một số phương pháp IPC để thay đổi các thông số của phần daemon của ứng dụng hoặc giết nó và khởi động lại nó với các tham số mới bất cứ khi nào người dùng thay đổi một số thiết lập trong phần GUI.Chưa kể đến báo cáo lỗi. Tôi sẽ phải sử dụng IPC hoặc hệ thống thoát mã hoặc tín hiệu hoặc tôi không biết những gì để nhận ra điều này. Nhiều biến chứng bao gồm cả rằng tôi cần wx cho dịch vụ "daemon" để hoạt động chính xác. Nó không phải là không thể, và tôi sẽ làm một cái gì đó như thế nếu tôi phải, nhưng tôi sẽ cố gắng trả lời của bạn đầu tiên. Tôi sẽ cho bạn biết những gì đã xảy ra. – Dalen

Trả lời

1

OK người, có một giải pháp tốt, tốt đẹp và chính xác mà không có bất kỳ rối tung xung quanh.

Thứ nhất, tôi muốn giải thích lý do tại sao quá trình Windows GUI chuyển sang nền khi wx.Frame.Show (MyFrame, False) được gọi.

Giải thích rất ngắn và bỏ qua chi tiết là Windows xem Cửa sổ và ứng dụng giống như vậy.

I.e. Phần tử chính của ứng dụng MS Windows là cửa sổ GUI chính của bạn.

Vì vậy, khi cửa sổ này bị ẩn, một ứng dụng không còn GUI nữa và tiếp tục chạy ẩn.

Mac OS X coi ứng dụng là ứng dụng của bạn và bất kỳ cửa sổ nào bạn chọn để đưa vào đó đều là con của nó để nói.

Điều này cho phép bạn chạy ứng dụng trong khi không hiển thị cửa sổ nhưng là thanh trình đơn, từ đó bạn có thể chọn một hành động mà sau đó sẽ tạo ra một cửa sổ cần thiết.

Rất tiện dụng cho người chỉnh sửa nơi bạn có thể mở nhiều tệp cùng lúc, mỗi tệp trong cửa sổ riêng và khi bạn đóng cửa sổ cuối cùng, bạn vẫn có thể mở một tệp mới hoặc tạo một tệp trống, v.v.

Do đó, phần tử chính của ứng dụng Mac OS X là bản thân ứng dụng và đó là lý do tại sao ứng dụng vẫn mở sau khi cửa sổ cuối bị ẩn, một cách hợp lý. Phá hủy thanh menu của nó cũng sẽ không giúp ích gì. Tên của ứng dụng sẽ vẫn tồn tại trong Dock và trong trình chuyển đổi ứng dụng và trong Force Quit. Bạn sẽ có thể chuyển sang nó và không làm gì cả. : D Nhưng, may mắn thay, Mac cung cấp cho chúng tôi chức năng để đặt nó vào nền mặc dù. Và hàm này đã được đề cập đến setApplicationActivationPolicy() từ đối tượng NSApp.

Sự cố là đặt tên của nó trong AppKit của Python, là NSApp.setActivationPolicy_(). Để làm phức tạp thêm các vấn đề, nó không có sẵn trực tiếp từ trình tương tác của Python nhưng nó phải được gọi ít nhất là từ một mô-đun được nhập khẩu.

Tại sao? Tôi không có ý kiến. Dù sao ở đây là một ví dụ hoàn chỉnh cho việc ném một ứng dụng vào nền mà sẽ làm việc trên Mac và Windows.

Tôi đã không thử trên Linux, kết hợp hành vi của Mac và Windows trong vấn đề trình bày một ứng dụng, vì vậy, cho dù chỉ ẩn một cửa sổ sẽ là đủ vẫn còn để được nhìn thấy.

Hãy thử và gửi bản chỉnh sửa để làm ví dụ về nhiều nền tảng hơn.

Ví dụ:



""" 
This app will show you small window with the randomly generated code that will confirm that reopened window is still the same app returned from background, 
and the button allowing you to send it to background. 
After you send it to background, wait 8 seconds and application will return to foreground again. 
Too prove that the application is continuing its work in the background, the app will call wx.Bell() every second. 
You should hear the sound while app is in the foreground and when it is in background too. 

Merry Christmas and a happy New Year! 
""" 

import wx 
import random, sys 

if sys.platform=="darwin": 
    from AppKit import NSBundle, NSApp, NSAutoreleasePool, NSApplicationActivationPolicyRegular, NSApplicationActivationPolicyProhibited 

    # Use Info.plist values to know whether our process started as daemon 
    # Also, change this dict in case anyone is later checking it (e.g. some module) 
    # Note: Changing this dict doesn't change Info.plist file 
    info = NSBundle.mainBundle().infoDictionary() 

    def SendToBackground(): 
     # Change info, just in case someone checks it later 
     info["LSUIElement"] = "1" 
     NSApp.setActivationPolicy_(NSApplicationActivationPolicyProhibited) 

    def ReturnToForeground(): 
     # Change info, just in case someone checks it later 
     info["LSUIElement"] = "0" 
     NSApp.setActivationPolicy_(NSApplicationActivationPolicyRegular) 

else: 
    # Simulate Mac OS X App - Info.plist 
    info = {"LSUIElement": "0"} # Assume non background at startup 
           # If programmer chose not to display GUI at startup then she/he should change this before calling ReturnToForeground() 
           # To preserve consistency and allow correct IsDaemon() answer 
    def SendToBackground(): 
     info["LSUIElement"] = "1" 

    def ReturnToForeground(): 
     info["LSUIElement"] = "0" 

def IsDaemon(): 
    return info["LSUIElement"]=="1" 

class Interface (wx.Frame): 
    def __init__ (self): 
     wx.Frame.__init__(self, None, -1, "Test", pos=(100, 100), size=(100, 100)) 
     wx.StaticText(self, -1, "Test code: "+str(random.randint(1000, 10000)), pos=(10, 10), size=(80, 20)) 
     b = wx.Button(self, -1, "DAEMONIZE ME", size=(80, 20), pos=(10, 50)) 
     wx.EVT_BUTTON(self, b.GetId(), self.OnDaemonize) 
     self.belltimer = wx.Timer(self) 
     wx.EVT_TIMER(self, self.belltimer.GetId(), self.OnBellTimer) 
     self.belltimer.Start(1000) 
     # On Mac OS X, you wouldn't be able to quit the app without the menu bar: 
     if sys.platform=="darwin": 
      self.SetMenuBar(wx.MenuBar()) 
     self.Show() 

    def OnBellTimer (self, e): 
     wx.Bell() 

    def OnDaemonize (self, e): 
     self.Show(False) 
     SendToBackground() 
     self.timer = wx.Timer(self) 
     wx.EVT_TIMER(self, self.timer.GetId(), self.OnExorcize) 
     self.timer.Start(8000) 

    def OnExorcize (self, e): 
     self.timer.Stop() 
     ReturnToForeground() 
     self.Show() 
     self.Raise() 

app = wx.App() 
i = Interface() 
app.MainLoop() 

Tất nhiên, ví dụ này có thể được bắt đầu từ thiết bị đầu cuối hoặc có cửa sổ CLI. Trong trường hợp này, điều khiển thiết bị đầu cuối trên chương trình của bạn sẽ vẫn mở trong khi ứng dụng chỉ xuất hiện và biến mất.

Để hoàn daemon GUI của bạn, bạn nên bắt đầu với nó pythonw (trên Windows) hoặc khởi động nó từ tập tin daemontest.pyw,

và trên máy Mac, bạn nên sử dụng:

% nohup python daemontest.py & 

hoặc bó nó với py2app hoặc sử dụng trình khởi chạy Python đi kèm với python.org phiên bản Python để khởi động daemontest.py mà không cần thiết bị đầu cuối.

Lưu ý: Ví dụ này bị lỗ hổng tương tự trên Mac OS X được đề cập trên các liên kết tôi đã cung cấp trong câu hỏi của mình. Tôi đề cập đến vấn đề tập trung sai và thanh trình đơn không xuất hiện ngay lập tức khi ứng dụng đến từ nền. Người dùng phải chuyển đổi và quay lại ứng dụng mới được trả về để ứng dụng hoạt động bình thường. Tôi hy vọng ai đó cũng sẽ giải quyết vấn đề này. Và sớm thôi. Nó khá là khó chịu.

Một lưu ý nữa: Nếu bạn có chủ đề đang chạy trong chương trình của mình, hãy tạm dừng các chủ đề trong khi thực hiện quá trình sao chép và loại trừ. Đặc biệt là nếu họ đang giao tiếp với một ứng dụng khác bằng cách sử dụng các sự kiện của Apple. Thành thật mà nói, một cái gì đó về wx.Timers nên được thực hiện quá. Nếu bạn không cẩn thận, bạn có thể gặp sự cố rò rỉ xung quanh NSAutoreleasePool và/hoặc SegmentationFault không tồn tại khi chấm dứt chương trình.

+0

Rất chi tiết và hữu ích! –

-1

Ok. Đây là mã để thực hiện những gì bạn muốn làm:

import AppKit 
info = AppKit.NSBundle.mainBundle().infoDictionary() 
info["LSUIElement"] = "1" 

Câu trả lời lộn xộn này bạn không muốn làm, nhưng tôi sẽ liệt kê nó. Trong tập tin info.plist thêm vào phím này:

<key>LSUIElement</key> 
<string>1</string> 

Một giải pháp daemonish hơn nhưng có nghĩa là nó không thể có một giao diện đồ họa, bạn thêm vào phím này để các info.plist file:

<key>LSBackgroundOnly</key> 
<string>1</string> 

Source

+0

@Dalen Nó có hoạt động không? –

+0

Không, tất nhiên là không! Thao tác các giá trị trong dict này chỉ có thể có hiệu lực trước khi wx.App() hoặc bất kỳ lớp ứng dụng nào khác được khởi tạo. wx.App() lấy giá trị ra khỏi đó và nếu có LSUIElement = 1 thì ứng dụng sẽ không được hiển thị cho người dùng. – Dalen

+0

-1 cho câu trả lời không đúng. : D! Tuy nhiên, bạn là một trong những người thậm chí dám thử cho một. Thậm chí có khả năng nó sẽ hoạt động, nếu dict() được đề cập không phải là một dict() nhưng đối tượng giống như dict có thay đổi thành một số. Sau đó thay đổi giá trị sẽ kích hoạt hiển thị/ẩn. Vì vậy, bravo cho rằng thử. Ngoài ra, câu trả lời của bạn đã giúp tôi vấp ngã giải pháp trong khi Googling như điên. Xem câu trả lời của tôi. Tôi cũng đã sử dụng A của bạn làm gợi ý về cách thực hiện một số nội dung trong khi làm việc trên giải pháp hoàn chỉnh. Vì vậy, tôi nghĩ rằng bạn rất tốt kiếm được 50 tiền thưởng. Cảm ơn! – Dalen

Các vấn đề liên quan