diff --git a/.gitignore b/.gitignore
index 926e849..159153d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,9 @@
*.pyc
*.egg-info
/build
+/win-build
/dist
__pycache__
+CMakeLists.txt.user
*.swp
*~
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..bd61c6a
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 2.8.11)
+
+option(ENABLE_TEST "Enable test program build" Off)
+
+find_package(Qt4 REQUIRED)
+set(CMAKE_CXX_FLAGS "-Wall --std=c++11 ${CMAKE_CXX_FLAGS}")
+
+add_subdirectory(src)
+
+
+if (ENABLE_TEST)
+enable_testing()
+add_subdirectory(tests)
+endif (ENABLE_TEST)
diff --git a/README.md b/README.md
index 3710dde..b22e36e 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,29 @@ DanmaQ is still under development, documents might be outdated.
### Dependencies
-`danmaQ` depends on `requests` and `PyQt4`, you can either install via `pip` or system package manager,
-if you use Windows, please download and install python3.4 and PyQt4 manually or download binary version from
+`danmaQ` depends on `Qt4` and `qjson`.
+
+For ArchLinux, run
+```
+yaourt -S danmaQ
+```
+
+For Ubuntu and Debian, run
+```
+sudo apt-get install libqjson0
+```
+
+For Fedora, run
+```
+sudo dnf install qjson
+```
+
+For OpenSUSE, run
+```
+sudo zypper install libqjson0
+```
+
+if you use Windows, download bundled binary version from
[releases page](https://github.com/bigeagle/danmaQ/releases/).
### Use TUNA Service
@@ -19,13 +40,9 @@ if you use Windows, please download and install python3.4 and PyQt4 manually or
First u need to create a channel, go to http://dm.tuna.moe/ and create a channel,
(let's use `ooxx` as the channel name and `passw0rd` as the password)
-then run `python danmaQ.py` and fill `http://dm.tuna.moe` to server,
+then run `danmaQ` and fill `http://dm.tuna.moe` to server,
and your channel name (`ooxx`) and channel password (`passw0rd)`.
-You can configure font and speed by through "preference" dialog.
-
-
-
then open http://dm.tuna.moe/ and click to your channel page, then post.
### Self Hosted Service
@@ -34,13 +51,13 @@ Clone https://github.com/tuna/gdanmaku-server and run `webserver.py` to start a
### Installation
-- **from source**: run `python setup.py install`.
+- **from source**: run `mkdir build && cd build && cmake .. && make && make install`.
- **windows binary**: https://github.com/bigeagle/danmaQ/releases/
- **Arch Linux**: [AUR](https://aur.archlinux.org/packages/danmaq-git/)
## TODO
-- [ ] Multi-Screen support
+- [x] Multi-Screen support
- [x] Chatting
- [ ] Deb package
- [ ] RPM package
diff --git a/cx_freeze.py b/cx_freeze.py
deleted file mode 100644
index e9d26b4..0000000
--- a/cx_freeze.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding:utf-8 -*-
-import os
-import re
-import sys
-import site
-from cx_Freeze import setup, Executable
-
-
-site_dir = site.getsitepackages()[1]
-
-# Dependencies are automatically detected, but it might need
-# fine tuning.
-buildOptions = dict(
- packages=['requests', 'danmaQ'],
- excludes=[],
- include_files=[
- 'LICENSE',
- 'danmaQ\images',
- # (os.path.join(site_dir, 'PyQt5', 'libEGL.dll'), 'libEGL.dll'),
- ],
- include_msvcr=True,
-)
-
-name = 'danmaQ'
-
-if sys.platform == 'win32':
- name = name + '.exe'
-
-base = None
-if sys.platform == "win32":
- base = "Win32GUI"
-
-executables = [
- Executable('danmaQ.py',
- base=base,
- icon=os.path.join("danmaQ", "images", "statusicon.ico"),
- targetName=name)
-]
-
-with open("README.md") as f:
- readme = f.read()
-
-__version__ = re.search(
- "__version__\s*=\s*'(.*)'",
- open('danmaQ/__init__.py').read(), re.M).group(1)
-assert __version__
-
-setup(
- name="danmaQ",
- version=__version__,
- description="Display danmaku on any screen",
- long_description=readme,
- author="Justin Wong",
- author_email="justin.w.xd@gmail.com",
- url="https://github.com/bigeagle/danmaQ/",
- license="GPLv3",
- options=dict(build_exe=buildOptions),
- executables=executables,
-)
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ.py b/danmaQ.py
deleted file mode 100755
index 492e9ed..0000000
--- a/danmaQ.py
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from danmaQ import app
-
-if __name__ == "__main__":
- app.main()
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ/__init__.py b/danmaQ/__init__.py
deleted file mode 100644
index 3543a78..0000000
--- a/danmaQ/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-__version__ = '0.13-dev'
diff --git a/danmaQ/app.py b/danmaQ/app.py
deleted file mode 100644
index bf6c337..0000000
--- a/danmaQ/app.py
+++ /dev/null
@@ -1,216 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-import sys
-from PyQt4 import QtGui
-from datetime import datetime
-from . import __version__
-from .danmaq_ui import Danmaku
-from .tray_icon import DanmaQTrayIcon, ICON_ENABLED
-from .settings import load_config, save_config, multiscreen_manager
-from .config_dialog import ConfigDialog
-from .subscriber import SubscribeThread
-
-
-class DanmakuApp(QtGui.QWidget):
- def __init__(self, parent=None):
- super(DanmakuApp, self).__init__(parent)
- self.setWindowTitle("Danmaku")
- self.setWindowIcon(QtGui.QIcon(ICON_ENABLED))
-
- self.trayIcon = DanmaQTrayIcon(self)
- self.trayIcon.show()
- self.config_dialog = ConfigDialog(self)
- self._options = load_config()
-
- layout = QtGui.QVBoxLayout()
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Server: ", self))
- self._server = QtGui.QLineEdit(
- self._options['http_stream_server'], self)
- hbox.addWidget(self._server)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Save As Default Server: ", self))
- self._save_server = QtGui.QCheckBox(self)
- hbox.addWidget(self._save_server)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Channel: ", self))
- self._chan = QtGui.QLineEdit("demo", self)
- hbox.addWidget(self._chan)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Password: ", self))
- self._passwd = QtGui.QLineEdit("", self)
- self._passwd.setEchoMode(QtGui.QLineEdit.Password)
- hbox.addWidget(self._passwd)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- self.config_button = QtGui.QPushButton("&Preferences", self)
- self.hide_button = QtGui.QPushButton("&Hide", self)
- self.main_button = QtGui.QPushButton("&Subscribe", self)
- hbox.addWidget(self.config_button)
- hbox.addWidget(self.hide_button)
- hbox.addWidget(self.main_button)
- layout.addLayout(hbox)
- self.setLayout(layout)
-
- self.config_button.released.connect(self.config_dialog.show)
- self.hide_button.released.connect(self.hide)
- self.main_button.released.connect(self.subscribe_danmaku)
- self.config_dialog.preferenceChanged.connect(self.apply_new_preference)
- self.trayIcon.toggleAction.triggered.connect(self.subscribe_danmaku)
- self.trayIcon.showAction.triggered.connect(self.show)
- self.trayIcon.configAction.triggered.connect(self.config_dialog.show)
- self.trayIcon.aboutAction.triggered.connect(self.show_about_dialog)
- self.trayIcon.exitAction.triggered.connect(self.close)
-
- self.workThread = None
- self.dms = {}
- self.alert_msg = None
- Danmaku.init_lineheight(self)
-
- def place_center(self):
- # Align Center
- screenGeo = QtGui.QDesktopWidget().screenGeometry()
- self.move(
- screenGeo.width() / 2 - self.width() / 2,
- screenGeo.height() / 2 - self.height() / 2,
- )
-
- def subscribe_danmaku(self):
- if QtGui.QDesktopWidget().screenCount() > 1:
- #self.config_dialog._to_extend_screen.setChecked(True)
- self._options['to_extend_screen'] = True
- else:
- #self.config_dialog._to_extend_screen.setChecked(False)
- self._options['to_extend_screen'] = False
- #save_config(self._options)
- Danmaku.set_options(self._options)
-
- if self.workThread is None or self.workThread.isFinished():
- self.workThread = SubscribeThread(
- "%s" % self._server.text(),
- "%s" % self._chan.text(),
- "%s" % self._passwd.text(),
- parent=self,
- )
- self.workThread.started.connect(self.on_subscription_started)
- self.workThread.finished.connect(self.on_subscription_finished)
- self.workThread.new_danmaku.connect(self.on_new_danmaku)
- self.workThread.new_alert.connect(self.on_new_alert)
- self.workThread.start()
- self.hide()
- else:
- self.workThread.terminate()
- self.workThread = None
-
- def on_new_danmaku(self, text, style, position):
- dm = Danmaku(
- text="%s" % text,
- style="%s" % style,
- position="%s" % position,
- parent=self
- )
-
- dm.exited.connect(self.delete_danmaku)
- self.dms[str(id(dm))] = dm
-
- def on_new_alert(self, msg):
- print(msg)
- self.alert_msg = "%s" % msg
- self.workThread.terminate()
- self.workThread = None
-
- def delete_danmaku(self, _id):
- self.dms.pop(str(_id))
-
- def on_subscription_started(self):
- if self._save_server.isChecked():
- opts = load_config()
- opts['http_stream_server'] = self._server.text()
- save_config(opts)
-
- self.main_button.setText("Unsubscribe")
- self.trayIcon.set_icon_running()
- self.trayIcon.showMessage(
- "DanmaQ",
- "Subscribing danmaku from {}".format(self.workThread.server)
- )
-
- def on_subscription_finished(self):
- _dms = [dm for _, dm in self.dms.items()]
- for dm in _dms:
- dm.hide()
- dm.clean_close()
- self.trayIcon.set_icon_not_running()
- self.main_button.setText("Subscribe")
- print(self.alert_msg)
- if self.alert_msg is not None:
- self.trayIcon.showMessage("DanmaQ", self.alert_msg)
- self.show()
- self.alert_msg = None
- else:
- self.trayIcon.showMessage("DanmaQ", "Subscription Finished")
-
- def apply_new_preference(self):
- pref = self.config_dialog.preferences()
- Danmaku.set_options(pref)
-
- def show_about_dialog(self):
- self.show()
- QtGui.QMessageBox.about(
- self,
- "About DanmaQ",
- """
- DanmaQ
-
Version: {version}
- Copyright © {year} Justin Wong
- Tsinghua University TUNA Association
-
-
- Source Code Available under GPLv3
-
-
- https://github.com/bigeagle/danmaQ
-
-
- """.format(
- version=__version__,
- year=datetime.now().strftime("%Y"),
- )
- )
-
-
-def init_multiscreen():
- dw = QtGui.QApplication.desktop()
- primary_screen_idx = dw.primaryScreen()
-
- # print("Primary screen: %d" % (primary_screen_idx, ))
- screen_geoms = [dw.screenGeometry(i) for i in range(dw.screenCount())]
- # print(screen_geoms)
-
- multiscreen_manager.set_primary(primary_screen_idx)
- multiscreen_manager.populate_geometries(screen_geoms)
-
-
-def main():
- import signal
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- signal.signal(signal.SIGTERM, signal.SIG_DFL)
- app = QtGui.QApplication(sys.argv)
- init_multiscreen()
- danmakuApp = DanmakuApp()
- danmakuApp.show()
- danmakuApp.place_center()
- sys.exit(app.exec_())
-
-if __name__ == "__main__":
- main()
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ/config_dialog.py b/danmaQ/config_dialog.py
deleted file mode 100644
index e03f7b0..0000000
--- a/danmaQ/config_dialog.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-from PyQt4 import QtCore, QtGui
-from PyQt4.QtCore import pyqtSignal
-from .tray_icon import ICON_ENABLED
-from .settings import load_config, save_config
-
-
-class ConfigDialog(QtGui.QDialog):
- preferenceChanged = pyqtSignal(name="preferenceChanged")
-
- def __init__(self, parent=None):
- super(ConfigDialog, self).__init__(parent)
- self.setWindowTitle("Danmaku")
- self.setWindowIcon(QtGui.QIcon(ICON_ENABLED))
-
- self._dft = load_config()
-
- layout = QtGui.QVBoxLayout()
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Font Family: "))
- self._font_family = QtGui.QFontComboBox(self)
- self._font_family.setCurrentFont(QtGui.QFont(self._dft['font_family']))
- hbox.addWidget(self._font_family)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Font Size: "))
- self._font_size = QtGui.QSpinBox(self)
- self._font_size.setValue(self._dft['font_size'])
- hbox.addWidget(self._font_size)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("To Extend Screen? "))
- self._to_extend_screen = QtGui.QCheckBox(self)
- if QtGui.QDesktopWidget().screenCount() > 1:
- self._to_extend_screen.setChecked(True)
- else:
- self._to_extend_screen.setChecked(False)
- self._to_extend_screen.setEnabled(False)
- hbox.addWidget(self._to_extend_screen)
- #for i in range(hbox.count()):
- # hbox.itemAt(i).hide()
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- hbox.addWidget(QtGui.QLabel("Speed Scale: "))
- self._speed = QtGui.QSlider(QtCore.Qt.Horizontal, self)
- self._speed.setTickInterval(1)
- self._speed.setMaximum(21)
- self._speed.setMinimum(4)
- self._speed.setValue(10)
- self._speed_indicator = QtGui.QLabel("1.0", self)
- self._speed.sliderMoved.connect(self.update_speed_indicator)
- hbox.addWidget(self._speed)
- hbox.addWidget(self._speed_indicator)
- layout.addLayout(hbox)
-
- hbox = QtGui.QHBoxLayout()
- self._save = QtGui.QPushButton("&Save && Apply", self)
- self._apply = QtGui.QPushButton("&Apply", self)
- self._cancel = QtGui.QPushButton("&Cancel", self)
- self._save.released.connect(self.save_preferences)
- self._apply.released.connect(self.emit_new_preferences)
- self._cancel.released.connect(self.hide)
- hbox.addWidget(self._save)
- hbox.addWidget(self._apply)
- hbox.addWidget(self._cancel)
- layout.addLayout(hbox)
-
- self.setLayout(layout)
-
- def update_speed_indicator(self):
- val = self._speed.value()
- self._speed_indicator.setText("{:.1f}".format(val/10.0))
-
- def preferences(self):
- return {
- 'font_family': self._font_family.currentText(),
- 'font_size': self._font_size.value(),
- 'speed_scale': self._speed.value() / 10.0,
- 'to_extend_screen': self._to_extend_screen.isChecked(),
- }
-
- def save_preferences(self):
- opts = load_config()
- new_opts = self.preferences()
- opts['font_family'] = new_opts['font_family']
- opts['font_size'] = new_opts['font_size']
- opts['speed_scale'] = new_opts['speed_scale']
- opts['to_extend_screen'] = new_opts['to_extend_screen']
- save_config(opts)
- self.emit_new_preferences()
-
- def emit_new_preferences(self):
- self.preferenceChanged.emit()
- self.hide()
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ/danmaq_ui.py b/danmaQ/danmaq_ui.py
deleted file mode 100644
index 4bfaddf..0000000
--- a/danmaQ/danmaq_ui.py
+++ /dev/null
@@ -1,358 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-import sys
-import re
-from cgi import escape
-from random import randint
-from threading import Lock
-
-from PyQt4 import QtCore, QtGui
-from PyQt4.QtCore import pyqtSignal
-
-from .settings import load_config, multiscreen_manager
-
-color_styles = {
- "white": ('rgb(255, 255, 255)', QtGui.QColor("black"), ),
- "black": ('rgb(0, 0, 0)', QtGui.QColor("white"), ),
- "blue": ('rgb(20, 95, 198)', QtGui.QColor("white"), ),
- "cyan": ('rgb(0, 255, 255)', QtGui.QColor("black"), ),
- "red": ('rgb(231, 34, 0)', QtGui.QColor("white"), ),
- "yellow": ('rgb(255, 221, 2)', QtGui.QColor("black"), ),
- "green": ('rgb(4, 202, 0)', QtGui.QColor("black"), ),
- "purple": ('rgb(128, 0, 128)', QtGui.QColor("white"), ),
-}
-
-OPTIONS = load_config()
-
-if sys.platform == "win32":
- import win32api
-
-
-class Danmaku(QtGui.QLabel):
- _lock = Lock()
- vertical_slots = None
- fly_slots = None
-
- _font_family = OPTIONS['font_family']
- _speed_scale = OPTIONS['speed_scale']
- _font_size = OPTIONS['font_size']
- _to_extend_screen = OPTIONS['to_extend_screen']
- _interval = 30
- _style_tmpl = "font-size: {font_size}pt;" \
- + "font-family: {font_family};" \
- + "font-weight: bold;" \
- + "color: {color}; "
- _lineheight = 0
-
- exited = pyqtSignal(str, name="exited")
-
- @classmethod
- def init_lineheight(cls, par=None):
- Danmaku("test", position='top', lifetime=10, parent=par)
-
- @classmethod
- def set_options(cls, opts):
- cls._font_family = opts['font_family']
- cls._font_size = opts['font_size']
- cls._speed_scale = opts['speed_scale']
- cls._to_extend_screen = opts['to_extend_screen']
-
- @classmethod
- def escape_text(cls, text):
- text = escape(text)
- text = re.sub(r'([^\\])\\n', r'\1
', text)
- text = re.sub(r'\\\\n', r'\\n', text)
- text = re.sub(r'\[s\](.+)\[/s\]', r'\1', text)
- return text
-
- def __init__(self, text="text", style='white', position='fly',
- lifetime=10*1000, parent=None):
- text = self.escape_text(text)
- super(Danmaku, self).__init__(text, parent)
-
- self._text = text
- self._style = style
- self._position = position
- self._lifetime = lifetime
-
- self.setWindowTitle("Danmaku")
- self.setStyleSheet("background:transparent; border:none;")
- self.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
- self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-
- self.setWindowFlags(
- QtCore.Qt.ToolTip
- | QtCore.Qt.FramelessWindowHint
- )
-
- if sys.platform == "win32":
- # Win32 Dark Magic, disable window drop shadows
- win32api.SetClassLong(
- self.winId().__int__(),
- -26,
- 0x0008 & ~0x00020000)
-
- self.init_text(text, style)
-
- self._width = self.frameSize().width()
- self._height = self.frameSize().height()
- self._slot = None
-
- self.screenIdx = multiscreen_manager.get_screen_idx(self._to_extend_screen)
- self.screenGeo = multiscreen_manager.geometry(self.screenIdx)
- # self._shift = QtGui.QDesktopWidget().availableGeometry(screen=0).width()
- self._offset_x = multiscreen_manager.get_offset_x(self.screenIdx, False)
- self._origin_y = multiscreen_manager.get_origin_y(self.screenIdx)
- with Danmaku._lock:
- if Danmaku.vertical_slots is None:
- Danmaku._lineheight = self._height
- Danmaku.vertical_slots = [0] * \
- ((self.screenGeo.height() - 20) // self._height)
- Danmaku.fly_slots = [0] * \
- ((self.screenGeo.height() - 20) // self._height)
-
- self.quited = False
- self.position_inited = False
- self.init_position()
-
- def init_text(self, text, style):
- # self.label = QtGui.QLabel(text, parent=self)
- tcolor, bcolor = color_styles.get(style, color_styles['white'])
-
- effect = QtGui.QGraphicsDropShadowEffect(self)
- effect.setBlurRadius(7)
- effect.setColor(bcolor)
- effect.setOffset(0, 0)
-
- self.setStyleSheet(
- self._style_tmpl.format(
- font_size=self._font_size,
- font_family=self._font_family,
- color=tcolor,
- )
- )
-
- self.setGraphicsEffect(effect)
- self.setContentsMargins(0, 0, 0, 0)
-
- # layout = QtGui.QVBoxLayout()
- # layout.addWidget(self.label, 0, QtCore.Qt.AlignVCenter)
- # layout.setContentsMargins(0, 0, 0, 0)
- # self.setLayout(layout)
-
- _msize = self.minimumSizeHint()
- # _msize.setHeight(self.label.height()+16)
- self.resize(_msize)
-
- def init_position(self):
- self.vslots = None
- self.fslots = None
- self.show()
- nlines = self._text.count('
') + 1
- # print("height: {}, lineheight: {}".format(self._height, self._lineheight))
- if self._position == 'fly':
- # NOTE: later offseted
- self.x = self.screenGeo.width()
-
- slot = None
- with Danmaku._lock:
- if nlines > 1:
- for i, v in enumerate(self.fly_slots):
- if v == 0:
- for j in range(nlines):
- try:
- self.fly_slots[i+j] = self
- except IndexError:
- break
-
- slot = i
- break
- else:
- m = len(self.fly_slots) // 2
- _upper = len(self.fly_slots) // 4
- for _ in range(m+1):
- i = randint(0, _upper)
- if self.fly_slots[i] == 0:
- self.fly_slots[i] = self
- slot = i
- break
- _upper *= 2
- if _upper > len(self.fly_slots) - 1:
- _upper = len(self.fly_slots) - 1
- # for i in range(m+1)[::-1]:
- # if self.fly_slots[i] == 0:
- # self.fly_slots[i] = self
- # slot = i
- # break
- # elif self.fly_slots[-i] == 0:
- # self.fly_slots[-i] = self
- # slot = len(self.fly_slots) - i
- # break
- # print(len(self.fly_slots), self.fly_slots)
-
- if slot is None:
- self.hide()
- QtCore.QTimer.singleShot(10, self.clean_close)
- return
- else:
- self.y = min(
- slot * self._lineheight + 20,
- self.screenGeo.height() - self._height - 20
- )
- self.fslots = [i+j for j in range(nlines)]
-
- self.step = (
- (self.screenGeo.width() + self._width)
- / (self._lifetime / self._interval)
- * self._speed_scale
- )
-
- QtCore.QTimer.singleShot(self._interval, self.fly)
-
- elif self._position == 'bottom':
- self.x = (self.screenGeo.width() - self._width) / 2
- got = False
- with Danmaku._lock:
- i = 0
- for i, v in enumerate(Danmaku.vertical_slots[::-1]):
- if v == 0:
- for j in range(nlines):
- try:
- Danmaku.vertical_slots[-(i+j+1)] = 1
- except IndexError:
- break
- self.vslots = [-(i+j+1) for j in range(nlines)]
- got = True
- break
-
- if not got:
- self.hide()
- QtCore.QTimer.singleShot(10, self.clean_close)
- return
- else:
- self.y = (self.screenGeo.height()
- + self._lineheight * self.vslots[-1] - 20)
- QtCore.QTimer.singleShot(self._lifetime, self.clean_close)
-
- elif self._position == 'top':
- self.x = (self.screenGeo.width() - self._width) / 2
- got = False
- with Danmaku._lock:
- i = 0
- for i, v in enumerate(Danmaku.vertical_slots):
- if v == 0:
- for j in range(nlines):
- try:
- Danmaku.vertical_slots[i+j] = 1
- except IndexError:
- break
- self.vslots = [i+j for j in range(nlines)]
- got = True
- break
- # else:
- # self.hide()
- # QtCore.QTimer.singleShot(1000, self.init_position)
- # return
-
- if not got:
- self.hide()
- QtCore.QTimer.singleShot(10, self.clean_close)
- return
- else:
- self.y = self._lineheight * self.vslots[0] + 20
- QtCore.QTimer.singleShot(self._lifetime, self.clean_close)
-
- # shift to the extend screen
- #if self._to_extend_screen:
- # print(self._shift)
- # self.x += self._shift
-
- # true multiscreen
- self.x += self._offset_x
- self.y += self._origin_y
- # print("initial: (%d, %d)" % (self.x, self.y, ))
-
- self.move(self.x, self.y)
- self.position_inited = True
-
- def fly(self):
- _x = int(self.x)
- self.x -= self.step
- x_dst = int(self.x)
- if (self.fly_slots[self.fslots[0]] == self
- and self.x + self._width < int(self.screenGeo.width() * 0.4)):
- for i in self.fslots:
- if i >= len(self.fly_slots):
- break
- Danmaku.fly_slots[i] = 0
-
- if self.x < -self._width:
- self.clean_close()
- else:
- QtCore.QTimer.singleShot(self._interval, self.fly)
-
- if _x != x_dst:
- self.move(x_dst, self.y)
-
- def clean_close(self):
- if self.quited is False:
- self.quited = True
-
- with Danmaku._lock:
- # print(Danmaku.count)
- if self.vslots is not None:
- for i in self.vslots:
- if i >= len(self.vertical_slots):
- break
- Danmaku.vertical_slots[i] = 0
-
- self.exited.emit(str(id(self)))
- self.destroy()
-
-
-class DanmakuTestApp(QtGui.QDialog):
- def __init__(self, parent=None):
- super(DanmakuTestApp, self).__init__(parent)
- self.setWindowTitle("Danmaku")
- self.lineedit = QtGui.QLineEdit("Text")
- self.style = QtGui.QLineEdit("blue")
- self.position = QtGui.QLineEdit("top")
- self.pushbutton = QtGui.QPushButton("Send")
- layout = QtGui.QVBoxLayout()
- layout.addWidget(self.lineedit)
- layout.addWidget(self.style)
- layout.addWidget(self.position)
- layout.addWidget(self.pushbutton)
- self.setLayout(layout)
- self.pushbutton.clicked.connect(self.new_danmaku)
- self.dms = {}
-
- def new_danmaku(self):
- text = self.lineedit.text()
- style = self.style.text()
- position = self.position.text()
- dm = Danmaku(text, style=style, position=position, parent=self)
- dm.exited.connect(self.delete_danmaku)
- self.dms[str(id(dm))] = dm
- dm.show()
-
- def delete_danmaku(self, _id):
- dm = self.dms.pop(_id)
- dm.close()
-
-
-def dm_test():
- import signal
- signal.signal(signal.SIGINT, signal.SIG_DFL)
- signal.signal(signal.SIGTERM, signal.SIG_DFL)
- app = QtGui.QApplication(sys.argv)
- danmakuTestApp = DanmakuTestApp()
- danmakuTestApp.show()
- sys.exit(app.exec_())
-
-if __name__ == "__main__":
- dm_test()
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ/images/statusicon.ico b/danmaQ/images/statusicon.ico
deleted file mode 100644
index 0891536..0000000
Binary files a/danmaQ/images/statusicon.ico and /dev/null differ
diff --git a/danmaQ/images/statusicon.png b/danmaQ/images/statusicon.png
deleted file mode 100644
index 6b45e74..0000000
Binary files a/danmaQ/images/statusicon.png and /dev/null differ
diff --git a/danmaQ/images/statusicon.svg b/danmaQ/images/statusicon.svg
deleted file mode 100644
index 17c0089..0000000
--- a/danmaQ/images/statusicon.svg
+++ /dev/null
@@ -1,78 +0,0 @@
-
-
-
-
diff --git a/danmaQ/images/statusicon_disabled.png b/danmaQ/images/statusicon_disabled.png
deleted file mode 100644
index f1f76ec..0000000
Binary files a/danmaQ/images/statusicon_disabled.png and /dev/null differ
diff --git a/danmaQ/settings.py b/danmaQ/settings.py
deleted file mode 100644
index d46cae1..0000000
--- a/danmaQ/settings.py
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-import os
-import sys
-import json
-
-DEFAULT_OPTIONS = {
- 'http_stream_server': "http://dm.tuna.moe",
- 'font_family': "WenQuanYi Micro Hei",
- 'font_size': 28,
- 'speed_scale': 1.0,
- 'to_extend_screen': False,
-}
-
-_xdg_cfg_dir = os.environ.get(
- "XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config"))
-
-_cfg_file = os.path.join(_xdg_cfg_dir, "danmaQ", "config.json")
-
-
-def load_config():
- options = dict(DEFAULT_OPTIONS.items())
-
- if os.path.exists(_cfg_file):
- try:
- with open(_cfg_file, 'rb') as f:
- if sys.version_info[0] == 3:
- s = bytes(f.read()).decode('utf-8')
- opts = json.loads(s)
- else:
- opts = json.load(f)
- options['to_extend_screen'] = opts['to_extend_screen']
- options['font_family'] = opts['font_family']
- options['font_size'] = opts['font_size']
- options['speed_scale'] = opts['speed_scale']
- options['http_stream_server'] = opts['http_stream_server']
- except:
- options = dict(DEFAULT_OPTIONS.items())
- save_config(options)
- else:
- if not os.path.exists(os.path.dirname(_cfg_file)):
- os.makedirs(os.path.dirname(_cfg_file))
- save_config(options)
-
- return options
-
-
-def save_config(options):
- with open(_cfg_file, 'wb') as f:
- if sys.version_info[0] == 3:
- s = json.dumps(options, indent=4)
- f.write(bytes(s, 'utf-8'))
- else:
- json.dump(options, f, indent=4)
-
-
-class MultiscreenManager(object):
- def __init__(self):
- self._primary_screen = 0
- self._geoms = []
-
- @property
- def primary_screen(self):
- return self._primary_screen
-
- def geometry(self, idx):
- return self._geoms[idx]
-
- def populate_geometries(self, geoms):
- self._geoms = geoms
-
- def set_primary(self, idx):
- self._primary_screen = idx
-
- def get_screen_idx(self, secondary=False):
- if len(self._geoms) < 2:
- return 0
-
- if len(self._geoms) >= 3:
- raise NotImplementedError
-
- if secondary:
- return 1 if self._primary_screen == 0 else 0
- else:
- return self._primary_screen
-
- def get_offset_x(self, idx, reversed=False):
- geom = self.geometry(idx)
- return geom.x() if not reversed else (geom.x() - geom.width())
-
- def get_origin_y(self, idx):
- geom = self.geometry(idx)
- return geom.y()
-
-
-multiscreen_manager = MultiscreenManager()
-
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ/subscriber.py b/danmaQ/subscriber.py
deleted file mode 100644
index e5d598c..0000000
--- a/danmaQ/subscriber.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#!/usr/bin/env python2
-# -*- coding:utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-import json
-import uuid
-import requests
-import time
-from PyQt4 import QtCore
-from PyQt4.QtCore import pyqtSignal
-
-
-class SubscribeThread(QtCore.QThread):
- new_danmaku = pyqtSignal(str, str, str, name="newDanmaku")
- new_alert = pyqtSignal(str, name="newAlert")
- _uuid = str(uuid.uuid1())
-
- def __init__(self, server, channel, passwd, parent=None):
- super(SubscribeThread, self).__init__(parent)
- self.server = str(server)
- self.channel = str(channel)
- self.passwd = str(passwd)
-
- def run(self):
- uri = "/api/v1.1/channels/{cname}/danmaku".format(cname=self.channel)
- if uri.startswith("/") and self.server.endswith("/"):
- server = self.server[:-1]
- else:
- server = self.server
-
- url = server + uri
- headers = {
- "X-GDANMAKU-SUBSCRIBER-ID": self._uuid,
- "X-GDANMAKU-AUTH-KEY": self.passwd,
- }
-
- while 1:
- try:
- res = requests.get(url, headers=headers)
- except requests.exceptions.ConnectionError:
- time.sleep(1)
- continue
- if res.status_code == 200 and res.text:
- try:
- dm_opts = json.loads(res.text)
- except:
- continue
- else:
- for dm in dm_opts:
- self.new_danmaku.emit(
- dm['text'], dm['style'], dm['position'])
-
- elif res.status_code == 403:
- self.new_alert.emit("Wrong Password!")
- elif res.status_code == 404:
- self.new_alert.emit("Channel does not exist!")
-
- def __del__(self):
- self.wait()
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/danmaQ/tray_icon.py b/danmaQ/tray_icon.py
deleted file mode 100644
index 63eafff..0000000
--- a/danmaQ/tray_icon.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-import os
-import sys
-from PyQt4 import QtGui
-
-__dir = os.path.dirname(sys.executable) \
- if getattr(sys, 'frozen', False) else \
- os.path.dirname(os.path.realpath(__file__))
-
-ICON_DIR = os.path.join(__dir, "images")
-
-ICON_ENABLED = os.path.join(ICON_DIR, "statusicon.png")
-ICON_DISABLED = os.path.join(ICON_DIR, "statusicon_disabled.png")
-
-
-class DanmaQTrayIcon(QtGui.QSystemTrayIcon):
-
- def __init__(self, parent=None):
- self._icon = QtGui.QIcon(ICON_DISABLED)
- super(DanmaQTrayIcon, self).__init__(self._icon, parent)
- menu = QtGui.QMenu(parent)
- self.toggleAction = menu.addAction("Toggle Subscription")
- self.showAction = menu.addAction("Show Main Window")
- self.configAction = menu.addAction("Preferences")
- self.aboutAction = menu.addAction("About")
- self.exitAction = menu.addAction("Exit")
- self.setContextMenu(menu)
- self.activated.connect(self.on_activate)
- self.show()
-
- def on_activate(self, reason):
- if reason == self.Trigger:
- if self.parent().isVisible():
- self.parent().hide()
- else:
- self.parent().show()
-
- def set_icon_not_running(self):
- self.setIcon(QtGui.QIcon(ICON_DISABLED))
-
- def set_icon_running(self):
- self.setIcon(QtGui.QIcon(ICON_ENABLED))
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/dm_ui_test.py b/dm_ui_test.py
deleted file mode 100644
index 7cb0adf..0000000
--- a/dm_ui_test.py
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-from danmaQ import danmaq_ui
-
-if __name__ == "__main__":
- danmaq_ui.dm_test()
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/setup.py b/setup.py
deleted file mode 100755
index c1ffa6f..0000000
--- a/setup.py
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding:utf-8 -*-
-import os
-import re
-
-try:
- from setuptools import setup
-except ImportError:
- from distutils.core import setup
-
-packages = ["danmaQ"]
-requires = ["requests"]
-
-with open("README.md") as f:
- readme = f.read()
-
-__version__ = re.search(
- "__version__\s*=\s*'(.*)'",
- open('danmaQ/__init__.py').read(), re.M).group(1)
-assert __version__
-
-setup(
- name="danmaQ",
- version=__version__,
- description="Display danmaku on any screen",
- long_description=readme,
- author="Justin Wong",
- author_email="justin.w.xd@gmail.com",
- url="https://github.com/bigeagle/danmaQ/",
- packages=packages,
- package_data={'': ['LICENCE', ], 'danmaQ': [os.path.join('images', '*.png'), ]},
- package_dir={'danmaQ': 'danmaQ'},
- include_package_data=True,
- install_requires=requires,
- license="GPLv3",
- entry_points={
- 'console_scripts': ['danmaQ = danmaQ.app:main'],
- },
-)
-
-# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/src/.ycm_extra_conf.py b/src/.ycm_extra_conf.py
new file mode 100644
index 0000000..7b56b5b
--- /dev/null
+++ b/src/.ycm_extra_conf.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python2
+# -*- coding:utf-8 -*-
+from __future__ import print_function, division, unicode_literals
+
+import os
+import ycm_core
+from clang_helpers import PrepareClangFlags
+
+# Set this to the absolute path to the folder (NOT the file!) containing the
+# compile_commands.json file to use that instead of 'flags'. See here for
+# more details: http://clang.llvm.org/docs/JSONCompilationDatabase.html
+# Most projects will NOT need to set this to anything; you can just change the
+# 'flags' list of compilation flags. Notice that YCM itself uses that approach.
+compilation_database_folder = ''
+
+# These are the compilation flags that will be used in case there's no
+# compilation database set.
+flags = [
+# THIS IS IMPORTANT! Without a "-std=" flag, clang won't know which
+# language to use when compiling headers. So it will guess. Badly. So C++
+# headers will be compiled as C headers. You don't want that so ALWAYS specify
+# a "-std=".
+# For a C project, you would set this to something like 'c99' instead of
+# 'c++11'.
+'-std=c++11',
+# ...and the same thing goes for the magic -x option which specifies the
+# language that the files to be compiled are written in. This is mostly
+# relevant for c++ headers.
+# For a C project, you would set this to 'c' instead of 'c++'.
+'-x',
+'c++',
+'-DQT_CORE_LIB',
+'-DQT_GUI_LIB',
+'-DQT_NETWORK_LIB',
+'-DQT_QML_LIB',
+'-DQT_QUICK_LIB',
+'-DQT_SQL_LIB',
+'-DQT_WIDGETS_LIB',
+'-DQT_XML_LIB',
+
+'-I', '/usr/lib/qt/mkspecs/linux-clang',
+'-I', '/usr/include/qt4',
+'-I', '/usr/include/qt4/QtCore',
+'-I', '/usr/include/qt4/QtGui',
+'-I', '/usr/include/qt4/QtNetwork',
+'-I', '/usr/include/qt4/QtWidgets',
+'-I', '/usr/include/qjson',
+'-I', '/usr/include/X11',
+'-I', '/usr/include/X11/extensions',
+
+'-I', '.',
+'-I', 'Tests',
+'-I', 'build',
+'-I', 'build/Tests'
+]
+
+if compilation_database_folder:
+ database = ycm_core.CompilationDatabase( compilation_database_folder )
+else:
+ database = None
+
+
+def DirectoryOfThisScript():
+ return os.path.dirname( os.path.abspath( __file__ ) )
+
+
+def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
+ if not working_directory:
+ return flags
+ new_flags = []
+ make_next_absolute = False
+ path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
+ for flag in flags:
+ new_flag = flag
+
+ if make_next_absolute:
+ make_next_absolute = False
+ if not flag.startswith( '/' ):
+ new_flag = os.path.join( working_directory, flag )
+
+ for path_flag in path_flags:
+ if flag == path_flag:
+ make_next_absolute = True
+ break
+
+ if flag.startswith( path_flag ):
+ path = flag[ len( path_flag ): ]
+ new_flag = path_flag + os.path.join( working_directory, path )
+ break
+
+ if new_flag:
+ new_flags.append( new_flag )
+ return new_flags
+
+
+def FlagsForFile( filename ):
+ if database:
+ # Bear in mind that compilation_info.compiler_flags_ does NOT return a
+ # python list, but a "list-like" StringVec object
+ compilation_info = database.GetCompilationInfoForFile( filename )
+ final_flags = PrepareClangFlags(
+ MakeRelativePathsInFlagsAbsolute(
+ compilation_info.compiler_flags_,
+ compilation_info.compiler_working_dir_ ),
+ filename )
+ else:
+ relative_to = DirectoryOfThisScript()
+ final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )
+
+ return {
+ 'flags': final_flags,
+ 'do_cache': True
+ }
+
+# vim: ts=4 sw=4 sts=4 expandtab
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..921d984
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,49 @@
+set(danmaQ_SOURCES
+ main.cpp
+ danmaku_ui.cpp
+ danmaku_window.cpp
+ danmaQ_app.cpp
+ subscriber.cpp
+ )
+
+set(danmaQ_HEADERS
+ subscriber.h
+ danmaku_ui.h
+ danmaku_window.h
+ danmaQ_app.h
+ danmaku.h
+ )
+
+set(danmaQ_RESOURCES icons.qrc)
+
+set(CMAKE_AUTOMOC ON)
+set(QT_USE_QTNETWORK TRUE)
+
+include(${QT_USE_FILE})
+add_definitions(${QT_DEFINITIONS})
+
+QT4_ADD_RESOURCES(danmaQ_RCC_SRCS ${danmaQ_RESOURCES})
+
+if (WIN32)
+ set(CMAKE_PREFIX_PATH "C:/Program Files/qjson/lib/cmake/qjson/")
+ find_library (QJSON_LIBRARIES
+ "C:/Program Files/qjson/lib"
+ ${QJSON_LIBRARY_DIRS}
+ ${LIB_INSTALL_DIR}
+ )
+ include_directories("C:/Program Files/qjson/include")
+ SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mwindows")
+endif(WIN32)
+
+find_package(QJSON REQUIRED)
+
+add_executable(danmaQ ${danmaQ_SOURCES} ${danmaQ_HEADERS_MOC} ${danmaQ_RCC_SRCS})
+
+set(DANMAQ_LIBRARIES ${QT_LIBRARIES} ${QT_QTNETWORK_LIBRARY} ${QJSON_LIBRARIES})
+
+if(CMAKE_SYSTEM_NAME STREQUAL Linux)
+ find_library(X_LIB Xext)
+ set(DANMAQ_LIBRARIES ${DANMAQ_LIBRARIES} ${X_LIB})
+endif()
+
+target_link_libraries(danmaQ ${DANMAQ_LIBRARIES})
diff --git a/src/danmaQ_app.cpp b/src/danmaQ_app.cpp
new file mode 100644
index 0000000..2fb85fc
--- /dev/null
+++ b/src/danmaQ_app.cpp
@@ -0,0 +1,225 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "danmaku.h"
+
+DMApp::DMApp() {
+
+ this->setWindowTitle("Danmaku");
+ this->setWindowIcon(QIcon(":icon_active.png"));
+ this->trayIcon = new DMTrayIcon(this);
+
+ QVBoxLayout* layout = new QVBoxLayout(this);
+
+ QHBoxLayout* hbox = new QHBoxLayout(this);
+ hbox->addWidget(new QLabel("Server: ", this));
+ this->server = new QLineEdit("http://dm.tuna.moe", this);
+ hbox->addWidget(this->server);
+ layout->addLayout(hbox);
+
+ hbox = new QHBoxLayout(this);
+ hbox->addWidget(new QLabel("Channel: ", this));
+ this->channel = new QLineEdit("demo", this);
+ hbox->addWidget(this->channel);
+ layout->addLayout(hbox);
+
+ hbox = new QHBoxLayout(this);
+ hbox->addWidget(new QLabel("Password: ", this));
+ this->passwd = new QLineEdit("", this);
+ this->passwd->setEchoMode(QLineEdit::Password);
+ hbox->addWidget(this->passwd);
+ layout->addLayout(hbox);
+
+ hbox = new QHBoxLayout(this);
+ this->hideBtn = new QPushButton("&Hide", this);
+ this->configBtn = new QPushButton("&config", this);
+ this->configBtn->setEnabled(false);
+ this->mainBtn = new QPushButton("&Subscribe", this);
+ hbox->addWidget(this->hideBtn);
+ hbox->addWidget(this->configBtn);
+ hbox->addWidget(this->mainBtn);
+ layout->addLayout(hbox);
+
+ this->setLayout(layout);
+
+
+ this->fontSize = 36;
+ this->lineHeight = this->fontSize * 1.2;
+ this->fontFamily = QString(
+ "WenQuanYi Micro Hei, Source Han Sans CN, WenQuanYi Zen Hei,"
+ "Microsoft YaHei, SimHei, "
+ "STHeiti, Hiragino Sans GB, "
+ "sans-serif"
+ );
+ this->speedScale = 1.0;
+
+ this->subscriber = NULL;
+ this->init_windows();
+
+ connect(this->mainBtn, SIGNAL(released()), this, SLOT(toggle_subscription()));
+ connect(this->hideBtn, SIGNAL(released()), this, SLOT(hide()));
+ connect(this->trayIcon->toggleAction, SIGNAL(triggered()), this, SLOT(toggle_subscription()));
+ connect(this->trayIcon->refreshScreenAction, SIGNAL(triggered()), this, SLOT(reset_windows()));
+ connect(this->trayIcon->showAction, SIGNAL(triggered()), this, SLOT(show()));
+ connect(this->trayIcon->aboutAction, SIGNAL(triggered()), this, SLOT(show_about_dialog()));
+ connect(this->trayIcon->exitAction, SIGNAL(triggered()), this, SLOT(close()));
+
+
+ this->show();
+ QDesktopWidget desktop;
+ auto center = desktop.screenGeometry(desktop.primaryScreen()).center();
+ this->move(center.x() - this->width()/2, center.y() - this->height()/2);
+}
+
+void DMApp::toggle_subscription() {
+ if (this->subscriber == NULL || this->subscriber->isFinished())
+ {
+ this->subscriber = new Subscriber(server->text(), channel->text(), passwd->text(), this);
+ for(auto w=this->dm_windows.begin(); w != this->dm_windows.end(); ++w) {
+ connect(
+ this->subscriber, SIGNAL(new_danmaku(QString, QString, QString)),
+ *w, SLOT(new_danmaku(QString, QString, QString))
+ );
+ }
+ connect(
+ this->subscriber, SIGNAL(started()),
+ this, SLOT(on_subscription_started())
+ );
+ connect(
+ this->subscriber, SIGNAL(finished()),
+ this, SLOT(on_subscription_stopped())
+ );
+ connect(
+ this->subscriber, SIGNAL(new_alert(QString)),
+ this, SLOT(on_new_alert(QString))
+ );
+ this->subscriber->start();
+
+ } else {
+ this->subscriber->mark_stop = true;
+ emit stop_subscription();
+ if (this->subscriber->wait(1000) == false) {
+ this->subscriber->terminate();
+ }
+ this->subscriber = NULL;
+ this->reset_windows();
+ }
+
+}
+
+void DMApp::init_windows() {
+ QDesktopWidget desktop;
+ this->screenCount = desktop.screenCount();
+ for (int i=0; idm_windows.append(w);
+
+ if (!(this->subscriber == NULL || this->subscriber->isFinished())) {
+ connect(
+ this->subscriber, SIGNAL(new_danmaku(QString, QString, QString)),
+ w, SLOT(new_danmaku(QString, QString, QString))
+ );
+ }
+ }
+}
+
+void DMApp::reset_windows() {
+ for(auto w=this->dm_windows.begin(); w != this->dm_windows.end(); ++w) {
+ delete *w;
+ }
+ this->dm_windows.clear();
+ this->init_windows();
+}
+
+void DMApp::on_subscription_started() {
+ myDebug << "Subscription Started";
+ this->hide();
+ this->trayIcon->set_icon_running();
+ this->mainBtn->setText("&Unsubscribe");
+ this->trayIcon->showMessage("Subscription Started", "Let's Go");
+}
+
+void DMApp::on_subscription_stopped() {
+ myDebug << "Subscription Stopped";
+ this->trayIcon->set_icon_stopped();
+ this->mainBtn->setText("&Subscribe");
+}
+
+void DMApp::on_new_alert(QString msg) {
+ myDebug << "Alert:" << msg;
+ this->trayIcon->showMessage("Ooops!", msg, QSystemTrayIcon::Critical);
+ this->subscriber->mark_stop = true;
+ emit stop_subscription();
+ if (this->subscriber->wait(1000) == false) {
+ this->subscriber->terminate();
+ }
+}
+
+void DMApp::show_about_dialog() {
+ this->show();
+ QMessageBox::about(
+ this, "About",
+ "DanmaQ"
+ "Copyright © 2015 Justin Wong
"
+ "Tsinghua University TUNA Association
"
+ " Source Code Available under GPLv3
"
+ ""
+ "https://github.com/bigeagle/danmaQ"
+ "
"
+ );
+}
+
+
+DMTrayIcon::DMTrayIcon(QWidget *parent)
+ :QSystemTrayIcon(parent)
+{
+ this->icon_running = QIcon(":icon_active.png");
+ this->icon_stopped = QIcon(":icon_inactive.png");
+ this->setIcon(this->icon_stopped);
+
+ QMenu* menu = new QMenu(parent);
+ this->toggleAction = menu->addAction("Toggle Subscription");
+ this->refreshScreenAction = menu->addAction("Refresh Screen");
+ this->showAction = menu->addAction("Show Main Window");
+ this->aboutAction = menu->addAction("About");
+ this->exitAction = menu->addAction("Exit");
+
+ this->setContextMenu(menu);
+
+ connect(
+ this, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
+ this, SLOT(on_activated(QSystemTrayIcon::ActivationReason))
+ );
+ this->show();
+}
+
+void DMTrayIcon::on_activated(QSystemTrayIcon::ActivationReason e) {
+ if(e == this->Trigger){
+ QWidget *parent = (QWidget *)this->parent();
+ if(parent == NULL) {
+ return;
+ }
+ if(parent->isVisible()) {
+ parent->hide();
+ } else {
+ parent->show();
+ }
+ }
+}
+
+void DMTrayIcon::set_icon_running() {
+ this->setIcon(this->icon_running);
+}
+
+void DMTrayIcon::set_icon_stopped() {
+ this->setIcon(this->icon_stopped);
+}
diff --git a/src/danmaQ_app.h b/src/danmaQ_app.h
new file mode 100644
index 0000000..cf0aeb7
--- /dev/null
+++ b/src/danmaQ_app.h
@@ -0,0 +1,69 @@
+#ifndef __DANMAQ_APP_H__
+#define __DANMAQ_APP_H__
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class Subscriber;
+
+class DMTrayIcon: public QSystemTrayIcon
+{
+ Q_OBJECT
+
+public:
+ DMTrayIcon(QWidget *parent=0);
+ QAction *toggleAction, *showAction, *aboutAction, *exitAction,
+ *refreshScreenAction;
+
+
+public slots:
+ void on_activated(QSystemTrayIcon::ActivationReason e);
+ void set_icon_running();
+ void set_icon_stopped();
+
+private:
+ QIcon icon_running, icon_stopped;
+
+};
+
+
+class DMApp: public QWidget
+{
+ Q_OBJECT
+
+public:
+ DMApp();
+
+ int lineHeight, fontSize, screenCount;
+ QString fontFamily;
+ float speedScale;
+
+ QLineEdit *server, *channel, *passwd;
+ QPushButton *configBtn, *hideBtn, *mainBtn;
+
+
+public slots:
+ void reset_windows();
+ void toggle_subscription();
+ void on_subscription_started();
+ void on_subscription_stopped();
+ void on_new_alert(QString msg);
+ void show_about_dialog();
+
+signals:
+ void stop_subscription();
+
+private:
+ QVector dm_windows;
+ Subscriber *subscriber;
+ DMTrayIcon *trayIcon;
+ void init_windows();
+
+
+};
+
+#endif
diff --git a/src/danmaku.h b/src/danmaku.h
new file mode 100644
index 0000000..90d0268
--- /dev/null
+++ b/src/danmaku.h
@@ -0,0 +1,11 @@
+#ifndef __DANMAKU_H__
+#define __DANMAKU_H__
+
+#include "danmaku_ui.h"
+#include "danmaku_window.h"
+#include "subscriber.h"
+#include "danmaQ_app.h"
+
+#define myDebug (qDebug() << "\x1b[34;1m" <<__PRETTY_FUNCTION__ << ":" << __LINE__ << "\x1b[0m")
+
+#endif
diff --git a/src/danmaku_ui.cpp b/src/danmaku_ui.cpp
new file mode 100644
index 0000000..72eea33
--- /dev/null
+++ b/src/danmaku_ui.cpp
@@ -0,0 +1,157 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include