Mercurial > minori
changeset 337:a7d4e5107531
dep/animone: REFACTOR ALL THE THINGS
1: animone now has its own syntax divergent from anisthesia,
making different platforms actually have their own sections
2: process names in animone are now called `comm' (this will
probably break things). this is what its called in bsd/linux
so I'm just going to use it everywhere
3: the X11 code now checks for the existence of a UTF-8 window title
and passes it if available
4: ANYTHING THATS NOT LINUX IS 100% UNTESTED AND CAN AND WILL BREAK!
I still actually need to test the bsd code. to be honest I'm probably
going to move all of the bsds into separate files because they're
all essentially different operating systems at this point
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Wed, 19 Jun 2024 12:51:15 -0400 |
parents | d260549151d6 |
children | f63dfa309380 |
files | Makefile.am dep/animone/README dep/animone/configure.ac dep/animone/data/players.animone dep/animone/data/players.anisthesia dep/animone/include/animone.h dep/animone/include/animone/media.h dep/animone/include/animone/player.h dep/animone/include/animone/types.h dep/animone/src/animone.cc dep/animone/src/fd.cc dep/animone/src/fd/bsd.cc dep/animone/src/fd/proc.cc dep/animone/src/fd/win32.cc dep/animone/src/fd/xnu.cc dep/animone/src/player.cc dep/animone/src/util/win32.cc dep/animone/src/win.cc dep/animone/src/win/quartz.cc dep/animone/src/win/win32.cc dep/animone/src/win/x11.cc rc/animone.qrc src/core/config.cc |
diffstat | 23 files changed, 971 insertions(+), 752 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile.am Wed Jun 19 06:32:25 2024 -0400 +++ b/Makefile.am Wed Jun 19 12:51:15 2024 -0400 @@ -106,18 +106,15 @@ $(top_srcdir)/rc/icons/24x24/question.png minori_linux_rc = \ - $(top_srcdir)/rc/sys/linux/Minori.desktop \ $(top_srcdir)/rc/sys/linux/Minori.png minori_osx_rc = \ - $(top_srcdir)/rc/sys/osx/Minori.app/Contents/Resources/Minori.icns \ $(top_srcdir)/rc/sys/osx/Minori.app/Contents/Info.plist \ $(top_srcdir)/rc/sys/osx/Minori.app/Contents/PkgInfo minori_win32_rc = \ $(top_srcdir)/rc/sys/win32/dark/dark.qrc \ $(top_srcdir)/rc/sys/win32/dark/dark.qss \ - $(top_srcdir)/rc/sys/win32/favicon.ico \ $(top_srcdir)/rc/sys/win32/version.rc minori_scripts = \
--- a/dep/animone/README Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/README Wed Jun 19 12:51:15 2024 -0400 @@ -33,7 +33,13 @@ what Animone will attempt to grab before falling back to the Quartz window name. On X11, Animone requires that the XRes extension is installed to retrieve window -PIDs. +PIDs. Animone will also attempt to retrieve window names in UTF-8 encoding; if +this is not possible it will be passed as whatever the current locale encoding +is. + +File paths will always be in UTF-8 on Windows and macOS. Linux and BSD treat +filenames as just a pile of bytes and it's no telling what encoding they're in +(but in most if not all cases they will be in UTF-8 as well) --- HISTORY --- Animone used to be under the name Animia, as in you'd contract anemia just from
--- a/dep/animone/configure.ac Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/configure.ac Wed Jun 19 12:51:15 2024 -0400 @@ -1,4 +1,4 @@ -AC_INIT([animone], [1.0.0]) +AC_INIT([animone], [2.0.0]) AC_CANONICAL_HOST @@ -29,16 +29,16 @@ build_windows=yes AC_CHECK_TOOL([WINDRES], [windres]) AC_SUBST(WINDRES) - AC_DEFINE([WIN32]) + AC_DEFINE([USE_WIN32]) ;; darwin*) # Mac OS X build_osx=yes - AC_DEFINE([MACOSX]) + AC_DEFINE([USE_MACOSX]) ;; linux*) build_linux=yes - AC_DEFINE([LINUX]) + AC_DEFINE([USE_LINUX]) ;; *) dnl BSDs @@ -46,10 +46,10 @@ AC_CHECK_LIB([kvm], [kvm_getfiles], [build_kvm=yes], [build_kvm=no]) if test "x$build_kvm" = "xyes"; then - AC_DEFINE([LIBKVM]) + AC_DEFINE([USE_LIBKVM]) AC_DEFINE([BSD]) elif test "x$build_libutil" = "xyes"; then - AC_DEFINE([LIBUTIL]) + AC_DEFINE([USE_LIBUTIL]) AC_DEFINE([BSD]) fi ;; @@ -58,7 +58,7 @@ if test "x$build_osx" != "xyes" && test "x$build_windows" != "xyes"; then PKG_CHECK_MODULES(XCB, [xcb xcb-res], [build_x11=yes], [build_x11=no]) if test "x$build_x11" = "xyes"; then - AC_DEFINE([X11]) + AC_DEFINE([USE_X11]) AC_SUBST([XCB_LIBS]) AC_SUBST([XCB_CFLAGS]) fi
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dep/animone/data/players.animone Wed Jun 19 12:51:15 2024 -0400 @@ -0,0 +1,580 @@ +# This file includes media player data for Anisthesia. It is used to detect +# running players and retrieve information about current media. +# +# Please read before editing this file: +# - Indentation is significant. You must use tabs rather than spaces. +# - Regular expressions begin with a '^' character. ECMAScript grammar is used. +# +# The latest version of this file can be found at: +# <https://github.com/erengy/anisthesia> +# +# This file is in the public domain. + +5KPlayer + windows: + win32: + Qt5QWindowIcon + executables: + win32: + 5KPlayer.exe + strategies: + open_files + +Ace Player HD + windows: + win32: + QWidget + executables: + win32: + ace_player.exe + strategies: + # Must be enabled from: Advanced Preferences -> Interface -> Main + # interfaces -> Qt -> Show playing item name in window title + # + # We use the last alternative to avoid detecting other windows such as + # Preferences dialog, which has the same generic class name. + window_title: + ^Ace Player HD.*|(.+) - Ace Player HD.*|.+ + +ALLPlayer + windows: + win32: + TApplication + executables: + win32: + ALLPlayer.exe + strategies: + open_files + +Baka MPlayer + windows: + win32: + Qt5QWindowIcon + executables: + win32: + Baka MPlayer.exe + strategies: + open_files + # We cannot avoid detecting other windows such as Preferences dialog, which + # has the same generic class name. + window_title: + ^Baka MPlayer|(.+) + +BESTplayer + windows: + win32: + TBESTplayerApp.UnicodeClass + executables: + win32: + BESTplayer.exe + strategies: + open_files + window_title: + ^BESTplayer.*|(.+) - BESTplayer.* + +bomi + windows: + win32: + Qt5QWindowGLOwnDCIcon + executables: + win32: + bomi.exe + strategies: + open_files + window_title: + ^bomi|(.+) - bomi + +BS.Player + windows: + win32: + BSPlayer + executables: + win32: + bsplayer.exe + strategies: + open_files + +DivX Player + windows: + win32: + Qt5QWindowIcon + QWidget + executables: + win32: + DivX Player.exe + DivX Plus Player.exe + strategies: + open_files + +GOM Player + windows: + win32: + GomPlayer1.x + GomPlayerPlus32_2.x + GomPlayerPlus64_2.x + executables: + win32: + GOM.exe + GOM64.exe + strategies: + open_files + window_title: + ^GOM Player(?: Plus)|(.+)(?:\[Subtitle\]) - GOM Player(?: Plus) + +Kantaris + windows: + win32: + ^WindowsForms10\.Window\.20008\.app\..+ + executables: + win32: + Kantaris.exe + KantarisMain.exe + strategies: + open_files + window_title: + ^Kantaris.*|(.+) \d{2}:\d{2}:\d{2} - \d{2}:\d{2}:\d{2} + +KMPlayer + windows: + win32: + KMPlayer 64X + TApplication + executables: + win32: + KMPlayer.exe + KMPlayer64.exe + strategies: + open_files + window_title: + ^(?:The )?KMPlayer|(?:\[\d+/\d+\] )?(.+) - (?:The )?KMPlayer|(.+) + +Kodi + windows: + win32: + Kodi + XBMC + executables: + win32: + kodi.exe + XBMC.exe + strategies: + open_files + +Light Alloy + windows: + win32: + TApplication + executables: + win32: + LA.exe + strategies: + open_files + window_title: + ^Light Alloy.*|(.+) - Light Alloy.* + +Media Player Classic + windows: + win32: + MediaPlayerClassicW + executables: + win32: + mplayerc.exe + mplayerc64.exe + strategies: + open_files + # Depends on: Options -> Player -> Title bar + window_title: + ^Media Player Classic|(.+) - Media Player Classic + +Media Player Classic Qute Theater + windows: + win32: + ^Qt.+QWindowIcon + executables: + win32: + mpc-qt.exe + strategies: + open_files + # Depends on: Options -> Player -> Title bar + # + # We use the last alternative to avoid detecting other windows such as + # Options dialog, which has the same generic class name. + window_title: + ^Media Player Classic Qute Theater|Media Player Classic Qute Theater - (.+)|.+ + +Memento + windows: + win32: + ^Qt.+QWindowIcon + executables: + win32: + memento.exe + strategies: + open_files + window_title: + ^Memento|(.+) - Memento + +Miro + windows: + win32: + gdkWindowToplevel + executables: + win32: + Miro.exe + strategies: + open_files + +MPC-BE + windows: + win32: + MediaPlayerClassicW + MPC-BE + executables: + win32: + mpc-be.exe + mpc-be64.exe + strategies: + open_files + # Depends on: Options -> Player -> Title bar + window_title: + ^MPC-BE.*|(.+) - MPC-BE.* + +MPC-HC + windows: + win32: + MediaPlayerClassicW + executables: + win32: + mpc-hc.exe + mpc-hc64.exe + # Some codec installers append "_nvo" to the filename, if NVIDIA Optimus + # is present on the system. Similarly, various guides recommend + # appending "-gpu", etc. in order to fix some GPU-related issues. + ^mpc-hc.+ + # LAV Filters Megamix + iris + shoukaku + strategies: + open_files + # Depends on: Options -> Player -> Title bar + window_title: + ^Media Player Classic Home Cinema|MPC-HC|(.+) + +MPCSTAR + windows: + win32: + ^wxWindow@.* + wxWindowClassNR + executables: + win32: + mpcstar.exe + strategies: + open_files + window_title: + ^MPCSTAR.*|(.+) - MPCSTAR.* + +MPDN + windows: + win32: + ^WindowsForms10\.Window\.8\.app\..+ + executables: + win32: + MediaPlayerDotNet.exe + strategies: + open_files + window_title: + ^MPDN - Media Player .NET \((?:32|64)-bit Edition\)|(.*) - MPDN \((?:32|64)-bit Edition\) + +mpv + windows: + win32: + mpv + executables: + win32: + mpv.exe + strategies: + open_files + # May be in an unexpected format if "--title" option is used. Ideally, it + # should return only "${filename}", "${path}" or "${media-title}". + window_title: + ^No file - mpv|(.+) - mpv|mpv - (.+) + +mpv.net + windows: + win32: + ^WindowsForms10\.Window\.8\.app\..+ + executables: + win32: + mpvnet.exe + strategies: + open_files + window_title: + ^mpv\.net.*|(.+) - mpv\.net.* + +MV2Player + windows: + win32: + TApplication + executables: + win32: + Mv2Player.exe + Mv2PlayerPlus.exe + strategies: + open_files + # Depends on: Options -> Player -> Constant app. title + window_title: + ^MV2 Player|(.+) + +PotPlayer + windows: + win32: + PotPlayer + PotPlayer64 + executables: + win32: + PotPlayer.exe + PotPlayer64.exe + PotPlayerMini.exe + PotPlayerMini64.exe + # LAV Filters Megamix + sumire.exe + zuikaku.exe + strategies: + open_files + window_title: + ^PotPlayer|(.+) - PotPlayer + +SMPlayer + windows: + win32: + # Qt5QWindowIcon, Qt5152QWindowIcon, etc. + ^Qt.+QWindowIcon + # Older versions + QWidget + executables: + win32: + smplayer.exe + smplayer2.exe + strategies: + # "open_files" strategy does not work here, because files are loaded by + # a child process of SMPlayer (mplayer or mpv, depending on the selected + # multimedia engine). + # + # We use the last alternative to avoid detecting other windows such as + # Preferences dialog, which has the same generic class name. + window_title: + ^SMPlayer|(.+) - SMPlayer|.+ + +Splash + windows: + win32: + DX_DISPLAY0 + executables: + win32: + Splash.exe + SplashLite.exe + strategies: + open_files + +SPlayer + windows: + win32: + MediaPlayerClassicW + executables: + win32: + splayer.exe + strategies: + open_files + # Does not work in theater mode. + window_title: + ^SPlayer|(?:\[(?:GPU Accel\+)?EVR\] )?(.+) - SPlayer + +UMPlayer + windows: + win32: + QWidget + executables: + win32: + umplayer.exe + strategies: + # "open_files" strategy does not work here, because files are loaded by + # a child process of UMPlayer (mplayer). + # + # We use the last alternative to avoid detecting other windows such as + # Preferences dialog, which has the same generic class name. + window_title: + ^UMPlayer|(.+) - UMPlayer|.+ + +VLC media player + windows: + win32: + # Qt5QWindowIcon, Qt5151QWindowIcon, etc. + ^Qt.+QWindowIcon + # Older versions + QWidget + # Skinnable interface + SkinWindowClass + x11: + vlc + executables: + win32: + vlc.exe + posix: + vlc + strategies: + open_files + # Must be enabled from: Advanced Preferences -> Interface -> Main + # interfaces -> Qt -> Show playing item name in window title + # + # We use the last alternative to avoid detecting other windows such as + # Preferences dialog, which has the same generic class name. + window_title: + ^VLC media player|(.+) - VLC media player|.+ + +WebTorrent Desktop + windows: + win32: + Chrome_WidgetWin_1 + executables: + win32: + WebTorrent.exe + strategies: + window_title: + ^WebTorrent(?: \(BETA\))?|Main Window|Preferences|About WebTorrent.*|(.+) + +Winamp + windows: + win32: + Winamp v1.x + executables: + win32: + winamp.exe + strategies: + open_files + window_title: + ^Winamp [\d.]+ Build \d+|\d+\. (.+) - Winamp(?: \[.+\])? + +Windows Media Player + windows: + win32: + WMPlayerApp + WMP Skin Host + executables: + win32: + wmplayer.exe + strategies: + open_files + +Zoom Player + windows: + win32: + TApplication + executables: + win32: + zplayer.exe + strategies: + open_files + window_title: + ^Zoom Player|(.+) - Zoom Player (?:FREE|MAX) + +################################################################################ +# Web browsers + +Brave Browser + windows: + win32: + Chrome_WidgetWin_1 + executables: + win32: + brave.exe + strategies: + ui_automation + window_title: + ^(.+) \(Private\)(?: - Brave)?|(.+) - Brave|(.+) + type: + web_browser + +Google Chrome + windows: + win32: + Chrome_WidgetWin_1 + executables: + win32: + chrome.exe + strategies: + ui_automation + window_title: + ^(.+) \(Incognito\)(?: - Google Chrome)?|(.+) - Google Chrome|(.+) + type: + web_browser + +Internet Explorer + windows: + win32: + IEFrame + executables: + win32: + iexplore.exe + strategies: + ui_automation + window_title: + ^(.+) - Internet Explorer(?: - \[InPrivate\])? + type: + web_browser + +Microsoft Edge + windows: + win32: + Chrome_WidgetWin_1 + executables: + win32: + msedge.exe + strategies: + ui_automation + window_title: + ^(.+) and \d+ more pages? - .+|(.+) - [^-]+ - Microsoft.*Edge|(.+) + type: + web_browser + +Mozilla Firefox + windows: + win32: + MozillaUIWindowClass + MozillaWindowClass + executables: + win32: + firefox.exe + strategies: + ui_automation + window_title: + ^(?:Mozilla Firefox|Firefox Developer Edition)|(.+) (?:-|—) (?:Mozilla Firefox|Firefox Developer Edition)(?: \(Private Browsing\))? + type: + web_browser + +Opera + windows: + win32: + Chrome_WidgetWin_1 + executables: + win32: + opera.exe + strategies: + ui_automation + window_title: + ^(.+) - Opera(?: \(Private\))? + type: + web_browser + +Waterfox + windows: + win32: + MozillaWindowClass + executables: + win32: + waterfox.exe + strategies: + ui_automation + window_title: + ^(.+) - Waterfox(?: \(Private Browsing\))? + type: + web_browser
--- a/dep/animone/data/players.anisthesia Wed Jun 19 06:32:25 2024 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,496 +0,0 @@ -# This file includes media player data for Anisthesia. It is used to detect -# running players and retrieve information about current media. -# -# Please read before editing this file: -# - Indentation is significant. You must use tabs rather than spaces. -# - Regular expressions begin with a '^' character. ECMAScript grammar is used. -# -# The latest version of this file can be found at: -# <https://github.com/erengy/anisthesia> -# -# This file is in the public domain. - -5KPlayer - windows: - Qt5QWindowIcon - executables: - 5KPlayer - strategies: - open_files - -Ace Player HD - windows: - QWidget - executables: - ace_player - strategies: - # Must be enabled from: Advanced Preferences -> Interface -> Main - # interfaces -> Qt -> Show playing item name in window title - # - # We use the last alternative to avoid detecting other windows such as - # Preferences dialog, which has the same generic class name. - window_title: - ^Ace Player HD.*|(.+) - Ace Player HD.*|.+ - -ALLPlayer - windows: - TApplication - executables: - ALLPlayer - strategies: - open_files - -Baka MPlayer - windows: - Qt5QWindowIcon - executables: - Baka MPlayer - strategies: - open_files - # We cannot avoid detecting other windows such as Preferences dialog, which - # has the same generic class name. - window_title: - ^Baka MPlayer|(.+) - -BESTplayer - windows: - TBESTplayerApp.UnicodeClass - executables: - BESTplayer - strategies: - open_files - window_title: - ^BESTplayer.*|(.+) - BESTplayer.* - -bomi - windows: - Qt5QWindowGLOwnDCIcon - executables: - bomi - strategies: - open_files - window_title: - ^bomi|(.+) - bomi - -BS.Player - windows: - BSPlayer - executables: - bsplayer - strategies: - open_files - -DivX Player - windows: - Qt5QWindowIcon - QWidget - executables: - DivX Player - DivX Plus Player - strategies: - open_files - -GOM Player - windows: - GomPlayer1.x - GomPlayerPlus32_2.x - GomPlayerPlus64_2.x - executables: - GOM - GOM64 - strategies: - open_files - window_title: - ^GOM Player(?: Plus)|(.+)(?:\[Subtitle\]) - GOM Player(?: Plus) - -Kantaris - windows: - ^WindowsForms10\.Window\.20008\.app\..+ - executables: - Kantaris - KantarisMain - strategies: - open_files - window_title: - ^Kantaris.*|(.+) \d{2}:\d{2}:\d{2} - \d{2}:\d{2}:\d{2} - -KMPlayer - windows: - KMPlayer 64X - TApplication - executables: - KMPlayer - KMPlayer64 - strategies: - open_files - window_title: - ^(?:The )?KMPlayer|(?:\[\d+/\d+\] )?(.+) - (?:The )?KMPlayer|(.+) - -Kodi - windows: - Kodi - XBMC - executables: - kodi - XBMC - strategies: - open_files - -Light Alloy - windows: - TApplication - executables: - LA - strategies: - open_files - window_title: - ^Light Alloy.*|(.+) - Light Alloy.* - -Media Player Classic - windows: - MediaPlayerClassicW - executables: - mplayerc - mplayerc64 - strategies: - open_files - # Depends on: Options -> Player -> Title bar - window_title: - ^Media Player Classic|(.+) - Media Player Classic - -Media Player Classic Qute Theater - windows: - ^Qt.+QWindowIcon - executables: - mpc-qt - strategies: - open_files - # Depends on: Options -> Player -> Title bar - # - # We use the last alternative to avoid detecting other windows such as - # Options dialog, which has the same generic class name. - window_title: - ^Media Player Classic Qute Theater|Media Player Classic Qute Theater - (.+)|.+ - -Memento - windows: - ^Qt.+QWindowIcon - executables: - memento - strategies: - open_files - window_title: - ^Memento|(.+) - Memento - -Miro - windows: - gdkWindowToplevel - executables: - Miro - strategies: - open_files - -MPC-BE - windows: - MediaPlayerClassicW - MPC-BE - executables: - mpc-be - mpc-be64 - strategies: - open_files - # Depends on: Options -> Player -> Title bar - window_title: - ^MPC-BE.*|(.+) - MPC-BE.* - -MPC-HC - windows: - MediaPlayerClassicW - executables: - mpc-hc - mpc-hc64 - # Some codec installers append "_nvo" to the filename, if NVIDIA Optimus - # is present on the system. Similarly, various guides recommend - # appending "-gpu", etc. in order to fix some GPU-related issues. - ^mpc-hc.+ - # LAV Filters Megamix - iris - shoukaku - strategies: - open_files - # Depends on: Options -> Player -> Title bar - window_title: - ^Media Player Classic Home Cinema|MPC-HC|(.+) - -MPCSTAR - windows: - ^wxWindow@.* - wxWindowClassNR - executables: - mpcstar - strategies: - open_files - window_title: - ^MPCSTAR.*|(.+) - MPCSTAR.* - -MPDN - windows: - ^WindowsForms10\.Window\.8\.app\..+ - executables: - MediaPlayerDotNet - strategies: - open_files - window_title: - ^MPDN - Media Player .NET \((?:32|64)-bit Edition\)|(.*) - MPDN \((?:32|64)-bit Edition\) - -mpv - windows: - mpv - executables: - mpv - strategies: - open_files - # May be in an unexpected format if "--title" option is used. Ideally, it - # should return only "${filename}", "${path}" or "${media-title}". - window_title: - ^No file - mpv|(.+) - mpv|mpv - (.+) - -mpv.net - windows: - ^WindowsForms10\.Window\.8\.app\..+ - executables: - mpvnet - strategies: - open_files - window_title: - ^mpv\.net.*|(.+) - mpv\.net.* - -MV2Player - windows: - TApplication - executables: - Mv2Player - Mv2PlayerPlus - strategies: - open_files - # Depends on: Options -> Player -> Constant app. title - window_title: - ^MV2 Player|(.+) - -PotPlayer - windows: - PotPlayer - PotPlayer64 - executables: - PotPlayer - PotPlayer64 - PotPlayerMini - PotPlayerMini64 - # LAV Filters Megamix - sumire - zuikaku - strategies: - open_files - window_title: - ^PotPlayer|(.+) - PotPlayer - -SMPlayer - windows: - # Qt5QWindowIcon, Qt5152QWindowIcon, etc. - ^Qt.+QWindowIcon - # Older versions - QWidget - executables: - smplayer - smplayer2 - strategies: - # "open_files" strategy does not work here, because files are loaded by - # a child process of SMPlayer (mplayer or mpv, depending on the selected - # multimedia engine). - # - # We use the last alternative to avoid detecting other windows such as - # Preferences dialog, which has the same generic class name. - window_title: - ^SMPlayer|(.+) - SMPlayer|.+ - -Splash - windows: - DX_DISPLAY0 - executables: - Splash - SplashLite - strategies: - open_files - -SPlayer - windows: - MediaPlayerClassicW - executables: - splayer - strategies: - open_files - # Does not work in theater mode. - window_title: - ^SPlayer|(?:\[(?:GPU Accel\+)?EVR\] )?(.+) - SPlayer - -UMPlayer - windows: - QWidget - executables: - umplayer - strategies: - # "open_files" strategy does not work here, because files are loaded by - # a child process of UMPlayer (mplayer). - # - # We use the last alternative to avoid detecting other windows such as - # Preferences dialog, which has the same generic class name. - window_title: - ^UMPlayer|(.+) - UMPlayer|.+ - -VLC media player - windows: - # Qt5QWindowIcon, Qt5151QWindowIcon, etc. - ^Qt.+QWindowIcon - # Older versions - QWidget - # Skinnable interface - SkinWindowClass - # X11 - vlc - executables: - vlc - strategies: - open_files - # Must be enabled from: Advanced Preferences -> Interface -> Main - # interfaces -> Qt -> Show playing item name in window title - # - # We use the last alternative to avoid detecting other windows such as - # Preferences dialog, which has the same generic class name. - window_title: - ^VLC media player|(.+) - VLC media player|.+ - -WebTorrent Desktop - windows: - Chrome_WidgetWin_1 - executables: - WebTorrent - strategies: - window_title: - ^WebTorrent(?: \(BETA\))?|Main Window|Preferences|About WebTorrent.*|(.+) - -Winamp - windows: - Winamp v1.x - executables: - winamp - strategies: - open_files - window_title: - ^Winamp [\d.]+ Build \d+|\d+\. (.+) - Winamp(?: \[.+\])? - -Windows Media Player - windows: - WMPlayerApp - WMP Skin Host - executables: - wmplayer - strategies: - open_files - -Zoom Player - windows: - TApplication - executables: - zplayer - strategies: - open_files - window_title: - ^Zoom Player|(.+) - Zoom Player (?:FREE|MAX) - -################################################################################ -# Web browsers - -Brave Browser - windows: - Chrome_WidgetWin_1 - executables: - brave - strategies: - ui_automation - window_title: - ^(.+) \(Private\)(?: - Brave)?|(.+) - Brave|(.+) - type: - web_browser - -Google Chrome - windows: - Chrome_WidgetWin_1 - executables: - chrome - strategies: - ui_automation - window_title: - ^(.+) \(Incognito\)(?: - Google Chrome)?|(.+) - Google Chrome|(.+) - type: - web_browser - -Internet Explorer - windows: - IEFrame - executables: - iexplore - strategies: - ui_automation - window_title: - ^(.+) - Internet Explorer(?: - \[InPrivate\])? - type: - web_browser - -Microsoft Edge - windows: - Chrome_WidgetWin_1 - executables: - msedge - strategies: - ui_automation - window_title: - ^(.+) and \d+ more pages? - .+|(.+) - [^-]+ - Microsoft.*Edge|(.+) - type: - web_browser - -Mozilla Firefox - windows: - MozillaUIWindowClass - MozillaWindowClass - executables: - firefox - strategies: - ui_automation - window_title: - ^(?:Mozilla Firefox|Firefox Developer Edition)|(.+) (?:-|—) (?:Mozilla Firefox|Firefox Developer Edition)(?: \(Private Browsing\))? - type: - web_browser - -Opera - windows: - Chrome_WidgetWin_1 - executables: - opera - strategies: - ui_automation - window_title: - ^(.+) - Opera(?: \(Private\))? - type: - web_browser - -Waterfox - windows: - MozillaWindowClass - executables: - waterfox - strategies: - ui_automation - window_title: - ^(.+) - Waterfox(?: \(Private Browsing\))? - type: - web_browser
--- a/dep/animone/include/animone.h Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/include/animone.h Wed Jun 19 12:51:15 2024 -0400 @@ -1,26 +1,25 @@ #ifndef ANIMONE_ANIMONE_H_ #define ANIMONE_ANIMONE_H_ +#include <cstdint> + #include "animone/media.h" #include "animone/player.h" #include "animone/types.h" namespace animone { -enum class ResultType { - Process, - Window -}; - struct Process { - internal::pid_t pid = 0; /* pid_t == DWORD on Windows, from <sys/types.h> everywhere else */ - std::string name; + internal::pid_t pid = 0; /* platform-dependent PID */ + ExecutablePlatform platform = ExecutablePlatform::Unknown; /* platform of the executable */ + std::string comm; /* the full filename of the executable */ }; struct Window { - unsigned int id = 0; - std::string class_name; - std::string text; /* title bar text */ + std::uint32_t id = 0; /* platform-dependent window id */ + WindowPlatform platform = WindowPlatform::Unknown; /* platform of the window */ + std::string class_name; /* class name on win32 and x11, bundle ID on macos */ + std::string text; /* title bar text if available */ }; struct Result {
--- a/dep/animone/include/animone/media.h Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/include/animone/media.h Wed Jun 19 12:51:15 2024 -0400 @@ -8,6 +8,7 @@ namespace animone { +// XXX where is this used? using media_time_t = std::chrono::milliseconds; enum class MediaInfoType {
--- a/dep/animone/include/animone/player.h Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/include/animone/player.h Wed Jun 19 12:51:15 2024 -0400 @@ -3,9 +3,24 @@ #include <string> #include <vector> +#include <map> namespace animone { +enum class ExecutablePlatform { + Posix, // Posix platforms that aren't OS X + Win32, // Windows + Xnu, // OS X + Unknown, // ... +}; + +enum class WindowPlatform { + Quartz, // OS X + Win32, // Windows + X11, // X11 + Unknown, // ... +}; + enum class Strategy { WindowTitle, OpenFiles, @@ -21,8 +36,8 @@ PlayerType type = PlayerType::Default; std::string name; std::string window_title_format; - std::vector<std::string> windows; - std::vector<std::string> executables; + std::map<WindowPlatform, std::vector<std::string>> windows; + std::map<ExecutablePlatform, std::vector<std::string>> executables; std::vector<Strategy> strategies; };
--- a/dep/animone/include/animone/types.h Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/include/animone/types.h Wed Jun 19 12:51:15 2024 -0400 @@ -2,16 +2,18 @@ #define ANIMONE_ANIMONE_TYPES_H_ /* define this as unsigned long (DWORD) on win32 so we - don't force the user to include <windows.h> or <IntBase.h> */ + * don't force the user to include <windows.h> or <IntBase.h> */ #ifdef _WIN32 +# include <cstdint> + namespace animone::internal { -typedef unsigned long pid_t; +using pid_t = std::uint32_t; } #else /* <sys/types.h> shouldn't be that big, right? */ # include <sys/types.h> namespace animone::internal { -typedef ::pid_t pid_t; +using pid_t = ::pid_t; } #endif
--- a/dep/animone/src/animone.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/animone.cc Wed Jun 19 12:51:15 2024 -0400 @@ -16,17 +16,19 @@ namespace internal { static bool IsExecutableInList(const Player& player, const Process& proc) { - for (const auto& pattern : player.executables) - if (util::CheckPattern(pattern, proc.name)) - return true; + for (const auto& [platform, comms] : player.executables) + for (const auto& comm : comms) + if (platform == proc.platform && util::CheckPattern(comm, proc.comm)) + return true; return false; } static bool IsWindowInList(const Player& player, const Window& window) { - for (const auto& pattern : player.windows) - if (util::CheckPattern(pattern, window.class_name)) - return true; + for (const auto& [platform, class_names] : player.windows) + for (const auto& class_name : class_names) + if (platform == window.platform && util::CheckPattern(class_name, window.class_name)) + return true; return false; }
--- a/dep/animone/src/fd.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/fd.cc Wed Jun 19 12:51:15 2024 -0400 @@ -1,18 +1,18 @@ #include "animone/fd.h" -#ifdef WIN32 +#ifdef USE_WIN32 # include "animone/fd/win32.h" #endif -#ifdef LINUX +#ifdef USE_LINUX # include "animone/fd/proc.h" #endif -#ifdef MACOSX +#ifdef USE_MACOSX # include "animone/fd/xnu.h" #endif -#ifdef BSD +#ifdef USE_BSD # include "animone/fd/bsd.h" #endif @@ -21,19 +21,19 @@ bool EnumerateOpenFiles(const std::set<pid_t>& pids, open_file_proc_t open_file_proc) { bool success = false; -#ifdef WIN32 +#ifdef USE_WIN32 success ^= win32::EnumerateOpenFiles(pids, open_file_proc); #endif -#ifdef LINUX +#ifdef USE_LINUX success ^= proc::EnumerateOpenFiles(pids, open_file_proc); #endif -#ifdef MACOSX +#ifdef USE_MACOSX success ^= xnu::EnumerateOpenFiles(pids, open_file_proc); #endif -#ifdef BSD +#ifdef USE_BSD success ^= bsd::EnumerateOpenFiles(pids, open_file_proc); #endif @@ -43,19 +43,19 @@ bool EnumerateOpenProcesses(process_proc_t process_proc) { bool success = false; -#ifdef WIN32 +#ifdef USE_WIN32 success ^= win32::EnumerateOpenProcesses(process_proc); #endif -#ifdef LINUX +#ifdef USE_LINUX success ^= proc::EnumerateOpenProcesses(process_proc); #endif -#ifdef MACOSX +#ifdef USE_MACOSX success ^= xnu::EnumerateOpenProcesses(process_proc); #endif -#ifdef BSD +#ifdef USE_BSD success ^= bsd::EnumerateOpenProcesses(process_proc); #endif @@ -65,19 +65,19 @@ bool GetProcessName(pid_t pid, std::string& name) { bool success = false; -#ifdef WIN32 +#ifdef USE_WIN32 success ^= win32::GetProcessName(pid, name); #endif -#ifdef LINUX +#ifdef USE_LINUX success ^= proc::GetProcessName(pid, name); #endif -#ifdef MACOSX +#ifdef USE_MACOSX success ^= xnu::GetProcessName(pid, name); #endif -#ifdef BSD +#ifdef USE_BSD success ^= bsd::GetProcessName(pid, name); #endif
--- a/dep/animone/src/fd/bsd.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/fd/bsd.cc Wed Jun 19 12:51:15 2024 -0400 @@ -93,7 +93,7 @@ } for (int i = 0; i < entries; i++) { - if (!process_proc({kinfo[i].ki_paddr->p_pid, Basename(kinfo[i].ki_paddr->p_comm)})) { + if (!process_proc({.platform = ExecutablePlatform::Posix, .pid = kinfo[i].ki_paddr->p_pid, .comm = Basename(kinfo[i].ki_paddr->p_comm)})) { kvm_close(kernel); return false; } @@ -123,7 +123,7 @@ return false; for (int i = 0; i < length / sizeof(result[0]); i++) - if (!process_proc({result[i].ki_pid, result[i].ki_comm})) + if (!process_proc({.platform = ExecutablePlatform::Posix, .pid = result[i].ki_pid, .comm = result[i].ki_comm})) return false; return true;
--- a/dep/animone/src/fd/proc.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/fd/proc.cc Wed Jun 19 12:51:15 2024 -0400 @@ -95,6 +95,7 @@ for (const auto& dir : std::filesystem::directory_iterator{PROC_LOCATION}) { Process proc; + proc.platform = ExecutablePlatform::Posix; try { proc.pid = util::StringToInt(dir.path().stem()); @@ -103,7 +104,7 @@ continue; } - if (!GetProcessName(proc.pid, proc.name)) + if (!GetProcessName(proc.pid, proc.comm)) continue; if (!process_proc(proc))
--- a/dep/animone/src/fd/win32.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/fd/win32.cc Wed Jun 19 12:51:15 2024 -0400 @@ -180,33 +180,36 @@ } bool GetProcessName(pid_t pid, std::string& name) { - std::wstring wname = GetProcessPath(pid); - if (wname.empty()) + std::string path = GetProcessPath(pid); + if (path.empty() || !VerifyProcessPath(path)) return false; - name = ToUtf8String(GetFileNameWithoutExtension(GetFileNameFromPath(wname))); + name = GetFileNameFromPath(path); + if (!VerifyProcessFileName(name)) + return false; + return true; } bool EnumerateOpenProcesses(process_proc_t process_proc) { - HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (hProcessSnap == INVALID_HANDLE_VALUE) + Handle process_snap(::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); + if (process_snap.get() == INVALID_HANDLE_VALUE) return false; PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); - if (!::Process32First(hProcessSnap, &pe32)) - return false; - - if (!process_proc({pe32.th32ProcessID, pe32.szExeFile})) + if (!::Process32First(process_snap.get(), &pe32)) return false; - while (::Process32Next(hProcessSnap, &pe32)) - if (!process_proc({pe32.th32ProcessID, pe32.szExeFile})) + do { + std::string name; + if (!GetProcessName(pe32.th32ProcessID, name)) + continue; + + if (!process_proc({.platform = ExecutablePlatform::Win32, .pid = pe32.th32ProcessID, .comm = name})) return false; - - ::CloseHandle(hProcessSnap); + } while (::Process32Next(process_snap.get(), &pe32)); return true; }
--- a/dep/animone/src/fd/xnu.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/fd/xnu.cc Wed Jun 19 12:51:15 2024 -0400 @@ -48,8 +48,8 @@ for (int i = 0; i < pids_size; i++) { std::string result; - osx::util::GetProcessName(pids[i], result); - if (!process_proc({pids[i], result})) + GetProcessName(pids[i], result); + if (!process_proc({.platform = ExecutablePlatform::Xnu, .pid = pids[i], .comm = result})) return false; }
--- a/dep/animone/src/player.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/player.cc Wed Jun 19 12:51:15 2024 -0400 @@ -5,19 +5,30 @@ #include <sstream> #include <string> #include <vector> +#include <optional> + +#include <iostream> namespace animone { namespace internal::parser { -enum class State { - ExpectPlayerName, - ExpectSection, - ExpectWindow, - ExpectExecutable, - ExpectStrategy, - ExpectType, - ExpectWindowTitle, +struct State { + enum class Name { + ExpectPlayerName, + ExpectSection, + ExpectWindowPlatform, + ExpectExecutablePlatform, + ExpectWindow, + ExpectExecutable, + ExpectStrategy, + ExpectType, + ExpectWindowTitle, + }; + + Name state = Name::ExpectPlayerName; + WindowPlatform window_platform = WindowPlatform::Unknown; + ExecutablePlatform executable_platform = ExecutablePlatform::Unknown; }; size_t GetIndentation(const std::string& line) { @@ -26,42 +37,64 @@ bool HandleIndentation(const size_t current, const std::vector<Player>& players, State& state) { // Each state has a definitive expected indentation - const auto expected = [&state]() -> size_t { + const auto expected = [](const State::Name& state) -> size_t { switch (state) { default: - case State::ExpectPlayerName: return 0; - case State::ExpectSection: return 1; - case State::ExpectWindow: - case State::ExpectExecutable: - case State::ExpectStrategy: - case State::ExpectType: return 2; - case State::ExpectWindowTitle: return 3; + case State::Name::ExpectPlayerName: return 0; + case State::Name::ExpectSection: return 1; + case State::Name::ExpectWindowPlatform: + case State::Name::ExpectExecutablePlatform: + case State::Name::ExpectStrategy: + case State::Name::ExpectType: return 2; + case State::Name::ExpectWindow: + case State::Name::ExpectExecutable: + case State::Name::ExpectWindowTitle: return 3; } - }(); + }(state.state); - if (current > expected) + if (current > expected) { + std::cerr << "animone: excessive indentation found" << std::endl; return false; // Disallow excessive indentation + } if (current < expected) { - auto fix_state = [&]() { state = !current ? State::ExpectPlayerName : State::ExpectSection; }; - switch (state) { - case State::ExpectWindow: + const std::optional<State::Name> st = [current, state]() -> std::optional<State::Name> { + switch (current) { + case 0: return State::Name::ExpectPlayerName; + default: + case 1: return State::Name::ExpectSection; + case 2: + switch (state.state) { + case State::Name::ExpectWindow: return State::Name::ExpectWindowPlatform; + case State::Name::ExpectExecutable: return State::Name::ExpectExecutablePlatform; + default: return std::nullopt; + } + } + }(); + if (!st.has_value()) + return false; + + switch (state.state) { + case State::Name::ExpectWindow: if (players.back().windows.empty()) return false; - fix_state(); + state.state = st.value(); break; - case State::ExpectExecutable: + case State::Name::ExpectExecutable: if (players.back().executables.empty()) return false; - fix_state(); + state.state = st.value(); break; - case State::ExpectStrategy: + case State::Name::ExpectStrategy: if (players.back().strategies.empty()) return false; - fix_state(); + state.state = st.value(); break; - case State::ExpectType: fix_state(); break; - case State::ExpectWindowTitle: return false; + case State::Name::ExpectType: + state.state = st.value(); + break; + case State::Name::ExpectWindowTitle: + return false; } } @@ -69,33 +102,63 @@ } bool HandleState(std::string& line, std::vector<Player>& players, State& state) { - switch (state) { - case State::ExpectPlayerName: + switch (state.state) { + case State::Name::ExpectPlayerName: players.push_back(Player()); players.back().name = line; - state = State::ExpectSection; + state.state = State::Name::ExpectSection; break; - case State::ExpectSection: { - static const std::map<std::string, State> sections = { - {"windows", State::ExpectWindow }, - {"executables", State::ExpectExecutable}, - {"strategies", State::ExpectStrategy }, - {"type", State::ExpectType }, + case State::Name::ExpectSection: { + static const std::map<std::string, State::Name> sections = { + {"windows", State::Name::ExpectWindowPlatform}, + {"executables", State::Name::ExpectExecutablePlatform}, + {"strategies", State::Name::ExpectStrategy }, + {"type", State::Name::ExpectType }, }; util::TrimRight(line, ":"); const auto it = sections.find(line); if (it == sections.end()) return false; - state = it->second; + state.state = it->second; + break; + } + + case State::Name::ExpectWindowPlatform: { + static const std::map<std::string, WindowPlatform> platforms = { + {"quartz", WindowPlatform::Quartz}, + {"win32", WindowPlatform::Win32}, + {"x11", WindowPlatform::X11}, + }; + util::TrimRight(line, ":"); + const auto it = platforms.find(line); + if (it == platforms.end()) + return false; + state.state = State::Name::ExpectWindow; + state.window_platform = it->second; break; } - case State::ExpectWindow: players.back().windows.push_back(line); break; + case State::Name::ExpectExecutablePlatform: { + static const std::map<std::string, ExecutablePlatform> platforms = { + {"posix", ExecutablePlatform::Posix}, + {"win32", ExecutablePlatform::Win32}, + {"macosx", ExecutablePlatform::Xnu}, + }; + util::TrimRight(line, ":"); + const auto it = platforms.find(line); + if (it == platforms.end()) + return false; + state.state = State::Name::ExpectExecutable; + state.executable_platform = it->second; + break; + } - case State::ExpectExecutable: players.back().executables.push_back(line); break; + case State::Name::ExpectWindow: players.back().windows[state.window_platform].push_back(line); break; - case State::ExpectStrategy: { + case State::Name::ExpectExecutable: players.back().executables[state.executable_platform].push_back(line); break; + + case State::Name::ExpectStrategy: { static const std::map<std::string, Strategy> strategies = { {"window_title", Strategy::WindowTitle }, {"open_files", Strategy::OpenFiles }, @@ -108,12 +171,12 @@ const auto strategy = it->second; players.back().strategies.push_back(strategy); switch (strategy) { - case Strategy::WindowTitle: state = State::ExpectWindowTitle; break; + case Strategy::WindowTitle: state.state = State::Name::ExpectWindowTitle; break; } break; } - case State::ExpectType: { + case State::Name::ExpectType: { static const std::map<std::string, PlayerType> types = { {"default", PlayerType::Default }, {"web_browser", PlayerType::WebBrowser}, @@ -125,9 +188,9 @@ break; } - case State::ExpectWindowTitle: + case State::Name::ExpectWindowTitle: players.back().window_title_format = line; - state = State::ExpectStrategy; + state.state = State::Name::ExpectStrategy; break; } @@ -145,9 +208,10 @@ std::istringstream stream(data); std::string line; size_t indentation = 0; - auto state = internal::parser::State::ExpectPlayerName; + internal::parser::State state; - while (std::getline(stream, line, '\n')) { + int ln = 1; + for (; std::getline(stream, line, '\n'); ln++) { if (line.empty()) continue; // Ignore empty lines @@ -159,11 +223,15 @@ if (line.empty() || line.front() == '#') continue; // Ignore empty lines and comments - if (!internal::parser::HandleIndentation(indentation, players, state)) + if (!internal::parser::HandleIndentation(indentation, players, state)) { + std::cerr << "animone: indentation: failed on line " << ln << std::endl; return false; + } - if (!internal::parser::HandleState(line, players, state)) + if (!internal::parser::HandleState(line, players, state)) { + std::cerr << "animone: state: failed on line " << ln << std::endl; return false; + } } return !players.empty();
--- a/dep/animone/src/util/win32.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/util/win32.cc Wed Jun 19 12:51:15 2024 -0400 @@ -47,7 +47,7 @@ return ret; } -std::wstring GetProcessPath(DWORD process_id) { +std::string GetProcessPath(DWORD process_id) { // If we try to open a SYSTEM process, this function fails and the last error // code is ERROR_ACCESS_DENIED. // @@ -68,22 +68,17 @@ return std::wstring(); buffer.resize(buf_size); - return buffer; + return ToUtf8String(buffer); } -std::wstring GetFileNameFromPath(const std::wstring& path) { +std::string GetFileNameFromPath(const std::string& path) { const auto pos = path.find_last_of(L"/\\"); return pos != std::wstring::npos ? path.substr(pos + 1) : path; } -std::wstring GetFileNameWithoutExtension(const std::wstring& filename) { - const auto pos = filename.find_last_of(L"."); - return pos != std::wstring::npos ? filename.substr(0, pos) : filename; -} - static std::wstring GetSystemDirectory() { PWSTR path_wch; - SHGetKnownFolderPath(FOLDERID_Windows, 0, NULL, &path_wch); + SHGetFolderPathW(NULL, CSIDL_WINDOWS, NULL, SHGFP_TYPE_CURRENT, &path_wch); std::wstring path_wstr(path_wch); CoTaskMemFree(path_wch); return path_wstr; @@ -99,7 +94,33 @@ std::wstring windir = GetSystemDirectory(); ::CharUpperBuffW(&windir.front(), windir.length()); + // XXX wtf is 4? return path.find(windir) == 4; } +bool VerifyProcessPath(const std::string& path) { + return !path.empty() && !IsSystemDirectory(path); +} + +bool VerifyProcessFileName(const std::string& name) { + static const std::set<std::string> invalid_names = { + // System files + "explorer.exe", // Windows Explorer + "taskeng.exe", // Task Scheduler Engine + "taskhost.exe", // Host Process for Windows Tasks + "taskhostex.exe", // Host Process for Windows Tasks + "taskmgr.exe", // Task Manager + "services.exe", // Service Control Manager + }; + + if (name.empty()) + return false; + + for (const auto& invalid_name : invalid_names) + if (util::EqualStrings(name, invalid_name)) + return false; + + return true; +} + } // namespace animone::internal::win32
--- a/dep/animone/src/win.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/win.cc Wed Jun 19 12:51:15 2024 -0400 @@ -1,14 +1,14 @@ #include "animone/win.h" -#ifdef WIN32 +#ifdef USE_WIN32 # include "animone/win/win32.h" #endif -#ifdef MACOSX +#ifdef USE_MACOSX # include "animone/win/quartz.h" #endif -#ifdef X11 +#ifdef USE_X11 # include "animone/win/x11.h" #endif @@ -17,15 +17,15 @@ bool EnumerateWindows(window_proc_t window_proc) { bool success = false; -#ifdef WIN32 +#ifdef USE_WIN32 success |= win32::EnumerateWindows(window_proc); #endif -#ifdef MACOSX +#ifdef USE_MACOSX success |= quartz::EnumerateWindows(window_proc); #endif -#ifdef X11 +#ifdef USE_X11 success |= x11::EnumerateWindows(window_proc); #endif
--- a/dep/animone/src/win/quartz.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/win/quartz.cc Wed Jun 19 12:51:15 2024 -0400 @@ -265,22 +265,17 @@ continue; Process proc; - { - CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerPID"), proc.pid); - if (!CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerName"), proc.name)) - fd::GetProcessName(proc.pid, proc.name); - } + proc.platform = ExecutablePlatform::Xnu; + CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerPID"), proc.pid); + if (!CFDictionaryGetValue(window, CFSTR("kCGWindowOwnerName"), proc.comm)) + fd::GetProcessName(proc.pid, proc.comm); Window win; - { - CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); + win.platform = WindowPlatform::Quartz; + CFDictionaryGetValue(window, CFSTR("kCGWindowNumber"), win.id); - if (!GetProcessBundleIdentifier(proc.pid, win.class_name)) - /* XXX is this right? */ - CFDictionaryGetValue(window, CFSTR("kCGWindowName"), win.class_name); - - GetWindowTitle(win.id, proc.pid, win.text); - } + GetProcessBundleIdentifier(proc.pid, win.class_name); + GetWindowTitle(win.id, proc.pid, win.text); if (!window_proc(proc, win)) { CFRelease(windows);
--- a/dep/animone/src/win/win32.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/win/win32.cc Wed Jun 19 12:51:15 2024 -0400 @@ -66,34 +66,17 @@ static bool VerifyClassName(const std::wstring& name) { static const std::set<std::wstring> invalid_names = { // System classes - L"#32770", // Dialog box - L"CabinetWClass", // Windows Explorer - L"ComboLBox", - L"DDEMLEvent", - L"DDEMLMom", - L"DirectUIHWND", - L"GDI+ Hook Window Class", - L"IME", - L"Internet Explorer_Hidden", - L"MSCTFIME UI", - L"tooltips_class32", - }; - - return !name.empty() && !invalid_names.count(name); -} - -static bool VerifyProcessPath(const std::wstring& path) { - return !path.empty() && !IsSystemDirectory(path); -} - -static bool VerifyProcessFileName(const std::wstring& name) { - static const std::set<std::wstring> invalid_names = { - // System files - L"explorer", // Windows Explorer - L"taskeng", // Task Scheduler Engine - L"taskhost", // Host Process for Windows Tasks - L"taskhostex", // Host Process for Windows Tasks - L"Taskmgr", // Task Manager + "#32770", // Dialog box + "CabinetWClass", // Windows Explorer + "ComboLBox", + "DDEMLEvent", + "DDEMLMom", + "DirectUIHWND", + "GDI+ Hook Window Class", + "IME", + "Internet Explorer_Hidden", + "MSCTFIME UI", + "tooltips_class32", }; return !name.empty() && !invalid_names.count(name); @@ -109,19 +92,17 @@ return TRUE; Window window; + window.platform = WindowPlatform::Win32; window.id = static_cast<unsigned int>(reinterpret_cast<ULONG_PTR>(hwnd)); window.text = ToUtf8String(GetWindowText(hwnd)); - - { - std::wstring class_name = GetWindowClassName(hwnd); - window.class_name = ToUtf8String(class_name); - if (!VerifyClassName(class_name)) - return TRUE; - } + window.class_name = ToUtf8String(GetWindowClassName(hwnd)); + if (!VerifyClassName(window.class_name)) + return TRUE; Process process; + process.platform = ExecutablePlatform::Win32; process.pid = GetWindowProcessId(hwnd); - GetProcessName(process.pid, process.name); + GetProcessName(process.pid, process.comm); auto& window_proc = *reinterpret_cast<window_proc_t*>(param); if (!window_proc(process, window))
--- a/dep/animone/src/win/x11.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/dep/animone/src/win/x11.cc Wed Jun 19 12:51:15 2024 -0400 @@ -13,6 +13,7 @@ #include <xcb/xcb.h> #include <climits> +#include <cassert> #include <cstdint> #include <cstring> #include <cstdlib> @@ -21,6 +22,7 @@ #include <memory> #include <chrono> +#include <unordered_map> #include <iostream> @@ -42,18 +44,32 @@ template<typename T> using XcbPtr = std::unique_ptr<T, XcbDestructor<T>>; -static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, +/* -------------------------------------------------------------- + * atom cruft */ + +enum class NeededAtom { + /* EWMH */ + NET_CLIENT_LIST, + NET_WM_NAME, + UTF8_STRING, + + /* ICCCM */ + WM_STATE, +}; + +static const std::unordered_map<NeededAtom, std::string> atom_strings = { + {NeededAtom::NET_CLIENT_LIST, "_NET_CLIENT_LIST"}, + {NeededAtom::NET_WM_NAME, "_NET_WM_NAME"}, + {NeededAtom::UTF8_STRING, "UTF8_STRING"}, + + {NeededAtom::WM_STATE, "WM_STATE"}, +}; + +using XcbAtoms = std::unordered_map<NeededAtom, xcb_atom_t>; + +static bool GetAllTopLevelWindowsEWMH(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { - const xcb_atom_t Atom__NET_CLIENT_LIST = [connection] { - static constexpr std::string_view name = "_NET_CLIENT_LIST"; - xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); - XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); - - xcb_atom_t atom = reply->atom; - - return atom; - }(); - if (Atom__NET_CLIENT_LIST == XCB_ATOM_NONE) + if (atoms.at(NeededAtom::NET_CLIENT_LIST) == XCB_ATOM_NONE) return false; // BTFO bool success = false; @@ -62,7 +78,7 @@ cookies.reserve(roots.size()); for (const auto& root : roots) - cookies.push_back(::xcb_get_property(connection, 0, root, Atom__NET_CLIENT_LIST, XCB_ATOM_ANY, 0L, UINT_MAX)); + cookies.push_back(::xcb_get_property(connection, 0, root, atoms.at(NeededAtom::NET_CLIENT_LIST), XCB_ATOM_ANY, 0L, UINT_MAX)); for (const auto& cookie : cookies) { XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, cookie, NULL)); @@ -81,88 +97,95 @@ return success; } -/* This is called on every window. What this does is: - * 1. Gets the tree of children - * 2. Searches all children recursively for a WM_STATE property - * 3. If that failed... return the original window - */ +/* This should be called with a list of toplevels for each root. */ static bool WalkWindows(xcb_connection_t* connection, int depth, xcb_atom_t Atom_WM_STATE, const xcb_window_t* windows, int windows_len, std::set<xcb_window_t>& result) { - /* The depth we should start returning at. */ - static constexpr int CUTOFF = 1; - - bool success = false; - + /* The level of depth we want to cut off past; since we want to go over each top level window, + * we cut off after we've passed the root window and the toplevel. */ + static constexpr int CUTOFF = 2; std::vector<xcb_query_tree_cookie_t> cookies; cookies.reserve(windows_len); for (int i = 0; i < windows_len; i++) cookies.push_back(::xcb_query_tree(connection, windows[i])); - for (const auto& cookie : cookies) { - XcbPtr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookie, NULL)); + for (int i = 0; i < cookies.size(); i++) { + /* XXX is it *really* okay to ask xcb for a cookie and then never ask for a reply? + * valgrind doesn't complain, so I'm not gonna care for now. */ + XcbPtr<xcb_query_tree_reply_t> query_tree_reply(::xcb_query_tree_reply(connection, cookies[i], NULL)); xcb_window_t* tree_children = ::xcb_query_tree_children(query_tree_reply.get()); int tree_children_len = ::xcb_query_tree_children_length(query_tree_reply.get()); - std::vector<xcb_get_property_cookie_t> state_property_cookies; - state_property_cookies.reserve(tree_children_len); + /* search for any window with a WM_STATE property */ + std::vector<xcb_get_property_cookie_t> state_cookies; + state_cookies.reserve(tree_children_len); for (int i = 0; i < tree_children_len; i++) - state_property_cookies.push_back( - ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 0)); + state_cookies.push_back( + ::xcb_get_property(connection, 0, tree_children[i], Atom_WM_STATE, Atom_WM_STATE, 0, 4L)); + + bool found = false; for (int i = 0; i < tree_children_len; i++) { - XcbPtr<xcb_get_property_reply_t> get_property_reply(::xcb_get_property_reply(connection, state_property_cookies[i], NULL)); + XcbPtr<xcb_get_property_reply_t> get_property_reply(::xcb_get_property_reply(connection, state_cookies[i], NULL)); + if (!get_property_reply) + continue; - /* X11 is unfriendly here. what this means is "did the property exist?" */ - if (get_property_reply->format || get_property_reply->type || get_property_reply->length) { + /* did we get valid data? */ + if (get_property_reply->type == Atom_WM_STATE || get_property_reply->format != 0 || get_property_reply->bytes_after != 0) { + int len = ::xcb_get_property_value_length(get_property_reply.get()); + if (len < sizeof(uint32_t)) + continue; + + uint32_t state = *reinterpret_cast<uint32_t*>(::xcb_get_property_value(get_property_reply.get())); + if (state != 1) // NormalState + continue; + result.insert(tree_children[i]); + found = true; if (depth >= CUTOFF) return true; - - success |= true; - continue; } } - if (WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result)) { - success |= true; - if (depth >= CUTOFF) - return true; + if (found) continue; - } + + bool res = WalkWindows(connection, depth + 1, Atom_WM_STATE, tree_children, tree_children_len, result); + + if (depth >= CUTOFF) + return res; } - return success; + return true; } -static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const std::vector<xcb_window_t>& roots, +static bool GetAllTopLevelWindowsICCCM(xcb_connection_t* connection, const XcbAtoms& atoms, const std::vector<xcb_window_t>& roots, std::set<xcb_window_t>& result) { - bool success = false; + if (atoms.at(NeededAtom::WM_STATE) == XCB_ATOM_NONE) + return false; - xcb_atom_t Atom_WM_STATE = [connection] { - static constexpr std::string_view name = "WM_STATE"; - xcb_intern_atom_cookie_t cookie = ::xcb_intern_atom(connection, true, name.size(), name.data()); - XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); + return WalkWindows(connection, 0, atoms.at(NeededAtom::WM_STATE), roots.data(), roots.size(), result); +} - xcb_atom_t atom = reply->atom; +static XcbAtoms InitializeAtoms(xcb_connection_t* connection) { + XcbAtoms atoms; + + std::unordered_map<NeededAtom, xcb_intern_atom_cookie_t> atom_cookies; - return atom; - }(); - if (Atom_WM_STATE == XCB_ATOM_NONE) - return success; - - std::vector<xcb_query_tree_cookie_t> cookies; - cookies.reserve(roots.size()); + for (const auto& [atom, str] : atom_strings) + atom_cookies[atom] = ::xcb_intern_atom(connection, 1, str.size(), str.data()); - for (const auto& root : roots) - cookies.push_back(::xcb_query_tree(connection, root)); + for (const auto& [atom, cookie] : atom_cookies) { + XcbPtr<xcb_intern_atom_reply_t> reply(::xcb_intern_atom_reply(connection, cookie, NULL)); + if (!reply) + atoms[atom] = XCB_ATOM_NONE; - for (const auto& cookie : cookies) - success |= WalkWindows(connection, 0, Atom_WM_STATE, roots.data(), roots.size(), result); + atoms[atom] = reply->atom; + } - return success; + return atoms; } bool EnumerateWindows(window_proc_t window_proc) { @@ -173,6 +196,8 @@ if (xcb_connection_has_error(connection)) return false; + XcbAtoms atoms = InitializeAtoms(connection); + std::set<xcb_window_t> windows; { std::vector<xcb_window_t> roots; @@ -182,15 +207,16 @@ roots.push_back(iter.data->root); } - if (!GetAllTopLevelWindowsEWMH(connection, roots, windows)) - GetAllTopLevelWindowsICCCM(connection, roots, windows); + if (!GetAllTopLevelWindowsEWMH(connection, atoms, roots, windows)) + GetAllTopLevelWindowsICCCM(connection, atoms, roots, windows); } struct WindowCookies { xcb_window_t window; - xcb_get_property_cookie_t class_property_cookie; - xcb_get_property_cookie_t name_property_cookie; - xcb_res_query_client_ids_cookie_t pid_property_cookie; + xcb_get_property_cookie_t class_name; + xcb_get_property_cookie_t name_utf8; + xcb_get_property_cookie_t name; + xcb_res_query_client_ids_cookie_t pid; }; std::vector<WindowCookies> window_cookies; @@ -200,19 +226,22 @@ xcb_res_client_id_spec_t spec = {.client = window, .mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID}; WindowCookies window_cookie = { - window, ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L), - ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX), - ::xcb_res_query_client_ids(connection, 1, &spec)}; + .window = window, + .class_name = ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0L, 2048L), + .name_utf8 = ::xcb_get_property(connection, 0, window, atoms[NeededAtom::NET_WM_NAME], atoms[NeededAtom::UTF8_STRING], 0L, UINT_MAX), + .name = ::xcb_get_property(connection, 0, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0L, UINT_MAX), + .pid = ::xcb_res_query_client_ids(connection, 1, &spec), + }; window_cookies.push_back(window_cookie); } for (const auto& window_cookie : window_cookies) { - Window win = {0}; + Window win; win.id = window_cookie.window; { /* Class name */ - XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.class_property_cookie, NULL)); + XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.class_name, NULL)); if (reply && reply->format == 8) { const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); @@ -226,32 +255,47 @@ } { /* Title text */ - XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.name_property_cookie, NULL)); + XcbPtr<xcb_get_property_reply_t> reply_utf8(::xcb_get_property_reply(connection, window_cookie.name_utf8, NULL)); + XcbPtr<xcb_get_property_reply_t> reply(::xcb_get_property_reply(connection, window_cookie.name, NULL)); + int utf8_len = ::xcb_get_property_value_length(reply_utf8.get()); + int len = ::xcb_get_property_value_length(reply.get()); - if (reply) { + if (reply_utf8 && utf8_len > 0) { + const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply_utf8.get())); + + win.text = std::string(data, utf8_len); + } else if (reply && len > 0) { const char* data = reinterpret_cast<const char*>(::xcb_get_property_value(reply.get())); - int len = ::xcb_get_property_value_length(reply.get()); win.text = std::string(data, len); } } - Process proc = {0}; + Process proc; + proc.platform = ExecutablePlatform::Posix; // not entirely correct, but whatever. who cares { /* PID */ - XcbPtr<xcb_res_query_client_ids_reply_t> reply(::xcb_res_query_client_ids_reply(connection, window_cookie.pid_property_cookie, NULL)); + XcbPtr<xcb_res_query_client_ids_reply_t> reply(::xcb_res_query_client_ids_reply(connection, window_cookie.pid, NULL)); if (reply) { xcb_res_client_id_value_iterator_t it = ::xcb_res_query_client_ids_ids_iterator(reply.get()); for (; it.rem; ::xcb_res_client_id_value_next(&it)) { if (it.data->spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { proc.pid = *::xcb_res_client_id_value_value(it.data); - GetProcessName(proc.pid, proc.name); /* fill this in if we can */ + GetProcessName(proc.pid, proc.comm); /* fill this in if we can */ break; } } } } + /* debug printing + std::cout << "window found: " << std::hex << win.id << std::dec << "\n" + << " name: " << win.text << "\n" + << " class: " << win.class_name << "\n" + << " pid: " << proc.pid << "\n" + << " comm: " << proc.name << std::endl; + */ + if (!window_proc(proc, win)) { ::xcb_disconnect(connection); return false;
--- a/rc/animone.qrc Wed Jun 19 06:32:25 2024 -0400 +++ b/rc/animone.qrc Wed Jun 19 12:51:15 2024 -0400 @@ -1,5 +1,5 @@ <!DOCTYPE rcc><RCC version="1.0"> <qresource> - <file alias="players.anisthesia">../dep/animone/data/players.anisthesia</file> + <file alias="players.animone">../dep/animone/data/players.animone</file> </qresource> </RCC>
--- a/src/core/config.cc Wed Jun 19 06:32:25 2024 -0400 +++ b/src/core/config.cc Wed Jun 19 12:51:15 2024 -0400 @@ -69,7 +69,7 @@ recognition.detect_media_players = toml::find_or(data, "Recognition", "Detect media players", true); { - QFile f(":/players.anisthesia"); + QFile f(":/players.animone"); if (!f.exists()) return false;