diff --git a/VRCT.py b/VRCT.py index 64cc44b6..1ed48de2 100644 --- a/VRCT.py +++ b/VRCT.py @@ -18,6 +18,7 @@ from audio_utils import get_input_device_list, get_output_device_list, get_defau from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from audio_transcriber import AudioTranscriber from translation import Translator +from notification import notification_xsoverlay_for_vrct class App(CTk): def __init__(self, *args, **kwargs): @@ -65,6 +66,7 @@ class App(CTk): self.INPUT_SPEAKER_RECORD_TIMEOUT = 3 self.INPUT_SPEAKER_PHRASE_TIMEOUT = 3 self.INPUT_SPEAKER_MAX_PHRASES = 10 + ## Parameter self.OSC_IP_ADDRESS = "127.0.0.1" self.OSC_PORT = 9000 @@ -78,6 +80,7 @@ class App(CTk): # Others self.ENABLE_AUTO_CLEAR_CHATBOX = False self.ENABLE_OSC = False + self.ENABLE_NOTICE_XSOVERLAY =False # load config if os_path.isfile(self.PATH_CONFIG) is not False: @@ -210,6 +213,9 @@ class App(CTk): if "ENABLE_AUTO_CLEAR_CHATBOX" in config.keys(): if type(config["ENABLE_AUTO_CLEAR_CHATBOX"]) is bool: self.ENABLE_AUTO_CLEAR_CHATBOX = config["ENABLE_AUTO_CLEAR_CHATBOX"] + if "ENABLE_NOTICE_XSOVERLAY" in config.keys(): + if type(config["ENABLE_NOTICE_XSOVERLAY"]) is bool: + self.ENABLE_NOTICE_XSOVERLAY = config["ENABLE_NOTICE_XSOVERLAY"] with open(self.PATH_CONFIG, 'w') as fp: config = { @@ -248,6 +254,7 @@ class App(CTk): "AUTH_KEYS": self.AUTH_KEYS, "MESSAGE_FORMAT": self.MESSAGE_FORMAT, "ENABLE_AUTO_CLEAR_CHATBOX": self.ENABLE_AUTO_CLEAR_CHATBOX, + "ENABLE_NOTICE_XSOVERLAY": self.ENABLE_NOTICE_XSOVERLAY, } json_dump(config, fp, indent=4) @@ -580,6 +587,8 @@ class App(CTk): # update textbox message receive log print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE") print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE") + if self.ENABLE_NOTICE_XSOVERLAY is True: + notification_xsoverlay_for_vrct(content=f"{voice_message}") self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) self.spk_print_transcript.daemon = True @@ -671,7 +680,7 @@ class App(CTk): print_textbox(self.textbox_message_send_log, f"{chat_message}", "SEND") # delete message in entry message box - if self.ENABLE_AUTO_CLEAR_CHATBOX == True: + if self.ENABLE_AUTO_CLEAR_CHATBOX is True: self.entry_message_box.delete(0, customtkinter.END) BREAK_KEYSYM_LIST = [ diff --git a/img/xsoverlay.png b/img/xsoverlay.png new file mode 100644 index 00000000..35c793ea Binary files /dev/null and b/img/xsoverlay.png differ diff --git a/locales.yml b/locales.yml index 79b5fd86..f4b80300 100644 --- a/locales.yml +++ b/locales.yml @@ -60,6 +60,7 @@ en: # tab Others label_checkbox_auto_clear_chatbox: "Auto clear chat box" + label_checkbox_notice_xsoverlay: "Notification XSOverlay" ja: @@ -125,6 +126,7 @@ ja: # tab Others label_checkbox_auto_clear_chatbox: "送信後はチャットボックスを空にする" + label_checkbox_notice_xsoverlay: "XSOverlayの通知機能を有効" ko: diff --git a/notification.py b/notification.py new file mode 100644 index 00000000..61e18725 --- /dev/null +++ b/notification.py @@ -0,0 +1,72 @@ +# ########################################################################################################################### +# DOCUMENT:https://xiexe.github.io/XSOverlayDocumentation/#/NotificationsAPI +# SOURCE:https://zenn.dev/eeharumt/scraps/95f49a62dd809a +# messageType: int = 0 # 1: ポップアップ通知, 2: メディアプレーヤー情報 +# index: int = 0 # メディアプレーヤーでのみ使用され、手首のアイコンを変更する +# timeout: float = 0.5 # 通知インジケータが表示され続ける時間[秒] +# height: float = 175 # 通知インジケータの高さ +# opacity: float = 1 # 通知インジケータの透明度。0.0-1.0の範囲で低いほど透明に +# volume: float = 0.7 # 通知音の大きさ +# audioPath: str = "" # 通知音ファイルのパス。規定音として"default", "error", "warning"を指定可能。空文字列で通知音なしにできる。 +# title: str = "" # 通知タイトル、リッチテキストフォーマットをサポート。 +# content: str = "" # 通知内容、リッチテキストフォーマットをサポート。省略することで小サイズ通知となる。 +# useBase64Icon: bool = False # TrueにすることでBase64の画像を表示する +# icon: str = "" # Base64画像イメージまたは画像ファイルパス。規定アイコンとして"default", "error", or "warning"を指定可能 +# sourceApp: str = "" # 通知したアプリ名(デバック用) +# ########################################################################################################################## + +import socket +import json +import base64 + +def notification_xsoverlay( + endpoint:tuple=("127.0.0.1", 42069), messageType:int=1, index:int=0, timeout:float=2, + height:float=120.0, opacity:float=1.0, volume:float=0.0, audioPath:str="", + title:str="", content:str="", useBase64Icon:bool=False, icon:str="default", sourceApp:str="" +) -> int: + + if icon in ["default", "error", "warning"]: + icon_data = icon + elif useBase64Icon: + try: + with open(icon, "rb") as f: + icon_data_bytes = f.read() + icon_data = base64.b64encode(icon_data_bytes).decode("utf-8") + except: + icon_data = "default" + else: + icon_data = icon + + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + data_msg = { + "messageType": messageType, + "index": index, + "timeout":timeout, + "height": height, + "opacity": opacity, + "volume": volume, + "audioPath": audioPath, + "title": title, + "content": content, + "useBase64Icon": useBase64Icon, + "icon": icon_data, + "sourceApp": sourceApp, + } + msg_str = json.dumps(data_msg) + response = sock.sendto(msg_str.encode("utf-8"), endpoint) + sock.close() + return response + +def notification_xsoverlay_for_vrct(content:str="") -> int: + response = notification_xsoverlay( + title="VRCT", + content=content, + useBase64Icon=True, + icon="./img/xsoverlay.png", + sourceApp="VRCT" + ) + return response + +if __name__ == "__main__": + notification_xsoverlay_for_vrct(content="notification test") \ No newline at end of file diff --git a/utils.py b/utils.py index 0e63f179..ac2eb35d 100644 --- a/utils.py +++ b/utils.py @@ -100,15 +100,15 @@ def widget_config_window_label_setter(self, language_yaml_data): "label_input_speaker_phrase_timeout", "label_input_speaker_max_phrases", - # tab Parameter "label_ip_address", "label_port", "label_authkey", "label_message_format", - + # tab Others - "label_checkbox_auto_clear_chatbox" + "label_checkbox_auto_clear_chatbox", + "label_checkbox_notice_xsoverlay", ] for name in widget_names: widget = getattr(self, name) diff --git a/window_config.py b/window_config.py index 89adde9a..7c8b2173 100644 --- a/window_config.py +++ b/window_config.py @@ -492,6 +492,11 @@ class ToplevelWindowConfig(CTkToplevel): self.parent.ENABLE_AUTO_CLEAR_CHATBOX = value save_json(self.parent.PATH_CONFIG, "ENABLE_AUTO_CLEAR_CHATBOX", self.parent.ENABLE_AUTO_CLEAR_CHATBOX) + def checkbox_notice_xsoverlay_callback(self): + value = self.checkbox_notice_xsoverlay.get() + self.parent.ENABLE_NOTICE_XSOVERLAY = value + save_json(self.parent.PATH_CONFIG, "ENABLE_NOTICE_XSOVERLAY", self.parent.ENABLE_NOTICE_XSOVERLAY) + def delete_window(self): self.checkbox_input_mic_threshold_check.deselect() self.checkbox_input_speaker_threshold_check.deselect() @@ -1381,7 +1386,7 @@ class ToplevelWindowConfig(CTkToplevel): # tab Others ## checkbox auto clear chat box - row += 1 + row = 0 self.label_checkbox_auto_clear_chatbox = CTkLabel( self.tabview_config.tab(config_tab_title_others), text=init_lang_text, @@ -1403,4 +1408,26 @@ class ToplevelWindowConfig(CTkToplevel): else: self.checkbox_auto_clear_chatbox.deselect() + # checkbox notice xsoverlay + row += 1 + self.label_checkbox_notice_xsoverlay = CTkLabel( + self.tabview_config.tab(config_tab_title_others), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=self.parent.FONT_FAMILY) + ) + self.label_checkbox_notice_xsoverlay.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.checkbox_notice_xsoverlay = CTkCheckBox( + self.tabview_config.tab(config_tab_title_others), + text="", + onvalue=True, + offvalue=False, + command=self.checkbox_notice_xsoverlay_callback, + font=CTkFont(family=self.parent.FONT_FAMILY) + ) + self.checkbox_notice_xsoverlay.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + if self.parent.ENABLE_NOTICE_XSOVERLAY is True: + self.checkbox_notice_xsoverlay.select() + else: + self.checkbox_notice_xsoverlay.deselect() widget_config_window_label_setter(self, language_yaml_data) \ No newline at end of file