I suddenly wanted to make an app in the menu bar. First of all, what is the menu bar here ↓ After studying what I can do, I decided to make a weather app.
I think many people will use Objective-C or Swift to develop the menu bar for the Mac. (Perhaps) There is rumps as a library to easily create a Mac menu bar in Python. https://github.com/jaredks/rumps The apps that use rumps are introduced, so I used them as a reference.
Install what you need to use rumps. Let's start with python3. (If you have already installed it, please skip it) First, install pyenv, which makes it easy to switch between python versions.
$ brew install pyenv
After installing pyenv, add the following to your .bash_profile in your home directory:
.bash_profile
export PYENV_ROOT=${HOME}/.pyenv
if [ -d "${PYENV_ROOT}" ]; then
export PATH=${PYENV_ROOT}/bin:$PATH
eval "$(pyenv init -)"
fi
$ source ~/.bash_profile
$ pyenv install 3.7.7
$ pyenv global 3.7.7
$ python -V
Python 3.7.7
$ pip -V
pip 19.2.3 from /Users/username/.pyenv/versions/3.7.7/lib/python3.7/site-packages/pip (python 3.7)
Next, we will build the environment with venv. venv is useful because you can manage the installation status of packages by pip for each project.
$ mkdir weather
$ cd weather
$ python -m venv venv
$ . venv/bin/activate
(venv) $ pip install rumps
Let's start with the rumps sample program below.
sample.py
import rumps
class RumpsTest(rumps.App):
@rumps.clicked("Hello World")
def hello_world(self, _):
rumps.alert("Hello World!")
@rumps.clicked("Check")
def check(self, sender):
sender.state = not sender.state
@rumps.clicked("Notify")
def sayhello(self, _):
rumps.notification("Hello", "hello", "hello world")
if __name__ == "__main__":
RumpsTest("Rumps Test").run()
(venv) $ python sample.py
When I run the program, the title of Rumps Test appears in the menu bar of my Mac. Click the title of Rumps Test and you will see the following.
@rumps.clicked("Hello World") #By writing before the function, a clickable Hello World is added to the list.
def hello_world(self, _):
rumps.alert("Hello World!") # Hello World!Alert is displayed.
sender.state = not sender.state #Every time Check is clicked, sender.state switches to 1 or 0.
rumps.notification("Hello", "hello","hello world") # title, subtitle,message is notified.
You can easily create a menu bar app like this. Let's actually make a weather app.
This time, we will use the livedoor Weather Hacks API to get the weather information. http://weather.livedoor.com/weather_hacks/webservice Use Python requests to fetch the data.
(venv) $ pip install requests
I will create it with the file name weather.py.
weather.py
import rumps
import requests
# Livedoor weather api
URL = 'http://weather.livedoor.com/forecast/webservice/json/v1'
class Weather(rumps.App):
@rumps.clicked("Get weather")
def get_weather(self, _):
payload = {'city': '130010'} #130010 is the area code for Tokyo in Tokyo.
data = requests.get(URL, params=payload).json()
tenki = data['forecasts'][0]['telop']
rumps.alert(tenki)
if __name__ == "__main__":
Weather("Weather").run()
Click Get weather to specify the Tokyo area of Tokyo, I was able to get the weather forecast and display it in the alert.
Since the area code of Tokyo is entered directly, it is inconvenient if you do not know the area code. Get the area code list and use Tokyo as the key so that you can enter the area code. Get the xml published by Livedoor and store it in the dictionary.
weather.py
import rumps
import requests
# Livedoor weather api
URL = 'http://weather.livedoor.com/forecast/webservice/json/v1'
# City list
XML = "http://weather.livedoor.com/forecast/rss/primary_area.xml"
class Weather(rumps.App):
@rumps.clicked("Get weather")
def get_weather(self, _):
self.area = self.get_city()
payload = {'city': self.area[('Tokyo', 'Tokyo')]} #You specified the area, not the area code.
data = requests.get(URL, params=payload).json()
tenki = data['forecasts'][0]['telop']
rumps.alert(tenki)
def get_city(self):
area = {}
src = et.fromstring(requests.get(XML).text)[0][12]
for c in src.findall("pref"):
for cc in c.findall("city"):
key = (c.attrib["title"], cc.attrib["title"])
area[key] = cc.attrib["id"]
return area # {('Prefectures', 'area'): 'Area code'}It is a dictionary in the form of.
if __name__ == "__main__":
Weather("Weather").run()
Tokyo I was able to know the weather in Tokyo, but would you like to see the weather in other areas as well? Next, let's make it possible to see the weather information of the area selected from the area list. I would like to click on an area to alert me to the weather information for that area.
weather.py
@rumps.clicked("Tokyo Tokyo")
def get_weather(self, _):
self.area = self.get_city()
payload = {'city': self.area[('Tokyo', 'Tokyo')]}
It's hard to write as above for all regions, so let's play with it a bit.
weather.py
class Weather(rumps.App):
def __init__(self, name):
super(Weather, self).__init__(
"Weather",
menu=[
rumps.MenuItem("Get weather", callback=self.get_weather)
]
)
def get_weather(self, _):
self.area = self.get_city()
payload = {'city': self.area[('Tokyo', 'Tokyo')]}
data = requests.get(URL, params=payload).json()
tenki = data['forecasts'][0]['telop']
rumps.alert(tenki)
I put in a constructor and removed the get_weather decoration. Instead of decoration, I used rumps.MenuItem and modified it to work in much the same way. I would like to be able to select a region from here.
weather.py
class Weather(rumps.App):
def __init__(self, name):
super(Weather, self).__init__(
"Weather",
menu=[
self.build_area()
]
)
def build_area(self):
self.area = self.get_city()
menu = rumps.MenuItem("Area")
for (pref, area), code in self.area.items():
title = "{} {}".format(pref, area)
menu[title] = rumps.MenuItem(title)
return menu
I think it looks like the image for the time being. Next, click on the area to display it in the alert.
weather.py
def build_area(self):
self.area = self.get_city()
menu = rumps.MenuItem("Area")
for (pref, area), code in self.area.items():
def get_weather(sender):
payload = {'city': code}
data = requests.get(URL, params=payload).json()
tenki = data['forecasts'][0]['telop']
rumps.alert(tenki)
title = "{} {}".format(pref, area)
menu[title] = rumps.MenuItem(title, callback=get_weather)
return menu
I tried to make it like this. For the time being, we will make this program into an app (in the form of weather.app).
(venv) $ pip install py2app
(venv) $ py2applet --make-setup weather.py
I think running the py2applet command generated setup.py. Add a little to setup.py.
setup.py
"""
This is a setup.py script generated by py2applet
Usage:
python setup.py py2app
"""
from setuptools import setup
APP = ['weather.py']
DATA_FILES = []
OPTIONS = {
'plist': {
'LSUIElement': True, #If specified, it will not be displayed in the Dock.
}
}
setup(
app=APP,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
)
(venv) $ python setup.py py2app
...
...
ValueError: '/Users/User name/.pyenv/versions/3.7.7/lib/libpython3.7.dylib' does not exist
I got an error in my environment. I don't have libpython3.7.dylib, so reinstall Python 3.7.7. At the time of installation, there is information that you can add --enable-shared, so I will try it.
(venv) $ deactive
$ cd
$ pyenv uninstall 3.7.7
$ PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install 3.7.7
$ ls /Users/User name/.pyenv/versions/3.7.7/lib/
libpython3.7m.dylib libpkgconfig python3.7
In python3.7.7 it was libpython3.7m.dylib. (In python3.8.0 it was libpython3.8.dylib properly.) I couldn't help it, so I renamed libpython3.7m.dylib to libpython3.7.dylib.
$ mv /Users/User name/.pyenv/versions/3.7.7/lib/libpython3.7m.dylib /Users/User name/.pyenv/versions/3.7.7/lib/libpython3.7.dylib
Run setup.py again.
$ cd weather
$ . venv/bin/activate
(venv) $ python setup.py py2app
...
...
Done!
I managed to get it right. I think that the menu bar application works by executing the application contained in dist.
How was that. You can easily create a weather app!
I have very much referred to the rumps app made by other people. https://shinaji.bitbucket.io/2014/09/08/manu_bar_app.html https://github.com/rbrich/computer-time/
Finally, I improved it to make it easier to use. https://github.com/hiro1112/WeatherApp
--I want to make it inoperable when the network is not connected I want to know if the network is connected without communicating from python. (I want to check every second)
――Since I'm using Python, I want to add machine learning functions! !! I want to use weather information to provide useful information to users of the app. I wanted to do something with text mining using information such as twitter.
--I can't write the test code at all ... The current coverage rate is terrible. I'll do it someday lol
Recommended Posts